feat: TextInput/TextArea borderless+ariaLabel+onKeyDown, Alert info, иконка info (0.11.0)

Для инлайн-ячеек таблиц СТ и info-баннеров при перекраске форм.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-08-26 08:39:25 +05:00
parent fa6fe8dbe1
commit f2e8108d1b
9 changed files with 66 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@tnsu/ui-kit",
"version": "0.10.0",
"version": "0.11.0",
"description": "TNSU shared UI components for SPFx web parts",
"author": "Timur Gorodetskiy",
"license": "ISC",
+6
View File
@@ -28,3 +28,9 @@
color: $red-500;
border-color: #fecaca;
}
.info {
background: $navy-50;
color: $navy-800;
border-color: $navy-100;
}
+5 -3
View File
@@ -3,7 +3,7 @@ import styles from './Alert.module.scss';
import { Icon } from '../Icon/Icon';
import { IconName } from '../Icon/iconNames';
type AlertVariant = 'warn' | 'ok' | 'err';
type AlertVariant = 'warn' | 'ok' | 'err' | 'info';
interface IAlertProps {
variant: AlertVariant;
@@ -13,13 +13,15 @@ interface IAlertProps {
const variantClass: Record<AlertVariant, string> = {
warn: styles.warn,
ok: styles.ok,
err: styles.err
err: styles.err,
info: styles.info
};
const variantIcon: Record<AlertVariant, IconName> = {
warn: 'warning',
ok: 'check',
err: 'close'
err: 'close',
info: 'info'
};
export const Alert: React.FC<IAlertProps> = ({ variant, children }) => {
+4
View File
@@ -147,6 +147,10 @@ export const IconSprite: React.FC = () => (
<path d="M12 2 1 21h22z" strokeLinejoin="round" />
<path d="M12 9v5M12 17h.01" />
</symbol>
<symbol id="i-info" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="9" />
<path d="M12 11v5M12 7.5h.01" />
</symbol>
<symbol id="i-check" viewBox="0 0 24 24">
<path d="M5 12l5 5L20 7" />
</symbol>
+1
View File
@@ -35,6 +35,7 @@ export const ICON_NAMES = [
'search',
'menu',
'warning',
'info',
'check',
'plus'
] as const;
+14
View File
@@ -21,3 +21,17 @@
color: $gray-400;
}
}
// Инлайн-ввод в ячейке таблицы: без рамки/фона, компактный, без ручного ресайза.
.borderless {
border-color: transparent;
background: transparent;
padding: 6px 8px;
min-height: 32px;
resize: none;
&:focus {
background: $white;
border-color: $gold-500;
}
}
+7 -2
View File
@@ -10,20 +10,25 @@ interface ITextAreaProps {
rows?: number;
/** Только чтение (режим согласующего). */
readOnly?: boolean;
/** Без рамки/фона — для инлайн-ввода в ячейках таблиц. */
borderless?: boolean;
/** a11y-подпись, когда видимого label нет. */
ariaLabel?: string;
}
/** Многострочный ввод (SharePoint Note). */
export const TextArea: React.FC<ITextAreaProps> = ({ id, value, onChange, placeholder, rows = 3, readOnly }) => {
export const TextArea: React.FC<ITextAreaProps> = ({ id, value, onChange, placeholder, rows = 3, readOnly, borderless, ariaLabel }) => {
const contextReadOnly = useFormReadOnly();
const effectiveReadOnly = readOnly ?? contextReadOnly;
return (
<textarea
id={id}
className={styles.textarea}
className={[styles.textarea, borderless ? styles.borderless : ''].filter(Boolean).join(' ')}
value={value}
placeholder={placeholder}
rows={rows}
readOnly={effectiveReadOnly}
aria-label={ariaLabel}
onChange={(e) => onChange(e.target.value)}
/>
);
+13
View File
@@ -19,3 +19,16 @@
color: $gray-400;
}
}
// Инлайн-ввод в ячейке таблицы: без рамки/фона, компактнее; рамка/фон проявляются на фокусе.
.borderless {
border-color: transparent;
background: transparent;
padding: 6px 8px;
border-radius: 4px;
&:focus {
background: $white;
border-color: $gold-500;
}
}
+15 -2
View File
@@ -19,6 +19,12 @@ interface ITextInputProps {
*/
min?: string | number;
max?: string | number;
/** Без рамки/фона — для инлайн-ввода в ячейках таблиц (рамка проявляется на фокусе). */
borderless?: boolean;
/** a11y-подпись, когда видимого label нет (ячейки таблиц). */
ariaLabel?: string;
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
autoFocus?: boolean;
}
/** Однострочный ввод. type='number'|'date' — для числовых полей и дат. */
@@ -30,20 +36,27 @@ export const TextInput: React.FC<ITextInputProps> = ({
placeholder,
readOnly,
min,
max
max,
borderless,
ariaLabel,
onKeyDown,
autoFocus
}) => {
const contextReadOnly = useFormReadOnly();
const effectiveReadOnly = readOnly ?? contextReadOnly;
return (
<input
id={id}
className={styles.input}
className={[styles.input, borderless ? styles.borderless : ''].filter(Boolean).join(' ')}
type={type}
value={value}
placeholder={placeholder}
readOnly={effectiveReadOnly}
aria-label={ariaLabel}
min={min}
max={max}
autoFocus={autoFocus}
onKeyDown={onKeyDown}
onChange={(e) => onChange(e.target.value)}
/>
);