@apliteni/apliteni-ui 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.46.0",
3
+ "version": "0.47.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -389,3 +389,21 @@ entire batch, and adds a new event. Timeline only calls `onUndo`; it does not
389
389
  modify history.
390
390
  Import both the kit CSS and React CSS. The Privileged story shows confirmation
391
391
  and a reversing event.
392
+
393
+ ## Fields
394
+
395
+ `TextField`, `TextArea`, `SelectField`, and `FileField` pair a label with a native
396
+ control. Each creates its own IDs and links hints or errors automatically. Use
397
+ native control props. `SelectField` accepts option children.
398
+
399
+ ```tsx
400
+ <TextField label="Weight" type="number" unit="kg" required />
401
+ <TextArea label="Notes" hint="Optional" />
402
+ <SelectField label="Currency"><option>EUR</option><option>USD</option></SelectField>
403
+ <FileField label="Attachment" accept=".pdf" hint="PDF, up to 5 MB."
404
+ onFileChange={file => setAttachment(file)} />
405
+ ```
406
+
407
+ `FileField` accepts one file from a picker or a drop and reports it through
408
+ `onFileChange`. Use `accept` and `hint` to describe allowed file types and size.
409
+ Your application must validate files before upload.
@@ -208,3 +208,307 @@
208
208
  .ui-timeline__undo:focus-visible {
209
209
  color: var(--pink);
210
210
  }
211
+
212
+ /* ../src/styles/input.css */
213
+ .ui-field {
214
+ display: block;
215
+ }
216
+ .ui-field__label {
217
+ display: block;
218
+ color: var(--strong);
219
+ font-size: var(--text-base);
220
+ font-weight: var(--weight-medium);
221
+ margin-bottom: 8px;
222
+ }
223
+ .ui-field__req {
224
+ color: var(--pink);
225
+ margin-left: 4px;
226
+ }
227
+ .ui-field__hint {
228
+ color: var(--text);
229
+ font-size: var(--text-sm);
230
+ margin-top: 7px;
231
+ line-height: 1.5;
232
+ }
233
+ .ui-field__error {
234
+ color: var(--pink);
235
+ font-size: var(--text-sm);
236
+ margin-top: 7px;
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 6px;
240
+ }
241
+ .ui-field__error svg {
242
+ width: 16px;
243
+ height: 16px;
244
+ flex: none;
245
+ stroke-width: 2.4;
246
+ }
247
+ .ui-input,
248
+ .ui-textarea,
249
+ .ui-select {
250
+ font-family: var(--font-sans);
251
+ font-size: var(--text-base);
252
+ color: var(--strong);
253
+ background: var(--surface-2);
254
+ border: 1px solid var(--border);
255
+ border-radius: var(--radius-md);
256
+ padding: 12px 15px;
257
+ width: 100%;
258
+ transition: border-color var(--dur-med) var(--ease), box-shadow var(--dur-med) var(--ease);
259
+ }
260
+ .ui-textarea {
261
+ resize: vertical;
262
+ min-height: 96px;
263
+ line-height: 1.55;
264
+ }
265
+ .ui-input:hover,
266
+ .ui-textarea:hover,
267
+ .ui-select:hover {
268
+ border-color: var(--border-strong);
269
+ }
270
+ .ui-input:focus-visible,
271
+ .ui-textarea:focus-visible,
272
+ .ui-select:focus-visible {
273
+ outline: 2px solid transparent;
274
+ border-color: var(--accent);
275
+ box-shadow: var(--ring);
276
+ }
277
+ .ui-input::placeholder,
278
+ .ui-textarea::placeholder {
279
+ color: var(--muted);
280
+ opacity: 0.9;
281
+ }
282
+ .ui-input:disabled,
283
+ .ui-select:disabled,
284
+ .ui-textarea:disabled {
285
+ background: var(--disabled-surface);
286
+ border-color: var(--disabled-border);
287
+ color: var(--disabled-ink);
288
+ cursor: not-allowed;
289
+ }
290
+ .ui-input.is-invalid,
291
+ .ui-select.is-invalid,
292
+ .ui-textarea.is-invalid {
293
+ border-color: var(--pink);
294
+ }
295
+ .ui-input-group {
296
+ position: relative;
297
+ display: flex;
298
+ align-items: center;
299
+ }
300
+ .ui-input-group .ui-input {
301
+ padding-left: 42px;
302
+ }
303
+ .ui-input-group__icon {
304
+ position: absolute;
305
+ left: 14px;
306
+ width: 17px;
307
+ height: 17px;
308
+ color: var(--muted);
309
+ pointer-events: none;
310
+ }
311
+ .ui-input-group__icon svg {
312
+ width: 100%;
313
+ height: 100%;
314
+ stroke: currentColor;
315
+ fill: none;
316
+ stroke-width: 2.2;
317
+ }
318
+ .ui-select {
319
+ appearance: none;
320
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a29db6' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
321
+ background-repeat: no-repeat;
322
+ background-position: right 15px center;
323
+ padding-right: 40px;
324
+ cursor: pointer;
325
+ }
326
+ :root[data-theme=light] .ui-select {
327
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235c6270' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
328
+ }
329
+ .ui-check {
330
+ display: inline-flex;
331
+ align-items: flex-start;
332
+ gap: 11px;
333
+ cursor: pointer;
334
+ font-size: var(--text-base);
335
+ color: var(--text);
336
+ line-height: 1.5;
337
+ }
338
+ .ui-check input {
339
+ appearance: none;
340
+ width: 19px;
341
+ height: 19px;
342
+ flex: none;
343
+ margin-top: 1px;
344
+ border: 1.5px solid var(--border-strong);
345
+ border-radius: var(--radius-xs);
346
+ background: var(--surface-2);
347
+ cursor: pointer;
348
+ transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
349
+ position: relative;
350
+ }
351
+ .ui-check input::before {
352
+ content: "";
353
+ position: absolute;
354
+ left: 50%;
355
+ top: 50%;
356
+ width: 24px;
357
+ height: 24px;
358
+ transform: translate(-50%, -50%);
359
+ }
360
+ .ui-check input[type=radio] {
361
+ border-radius: 50%;
362
+ }
363
+ .ui-check input:hover {
364
+ border-color: var(--accent);
365
+ }
366
+ .ui-check input:checked {
367
+ background: var(--accent);
368
+ border-color: var(--accent);
369
+ }
370
+ .ui-check input[type=checkbox]:checked::after {
371
+ content: "";
372
+ position: absolute;
373
+ left: 5px;
374
+ top: 1.5px;
375
+ width: 5px;
376
+ height: 10px;
377
+ border: solid var(--accent-contrast);
378
+ border-width: 0 2px 2px 0;
379
+ transform: rotate(45deg);
380
+ }
381
+ .ui-check input[type=radio]:checked::after {
382
+ content: "";
383
+ position: absolute;
384
+ inset: 4px;
385
+ border-radius: 50%;
386
+ background: var(--accent-contrast);
387
+ }
388
+ .ui-check input:focus-visible {
389
+ outline: 2px solid transparent;
390
+ box-shadow: var(--ring);
391
+ }
392
+ .ui-switch {
393
+ --sw-w: 44px;
394
+ --sw-h: 26px;
395
+ position: relative;
396
+ display: inline-block;
397
+ width: var(--sw-w);
398
+ height: var(--sw-h);
399
+ flex: none;
400
+ }
401
+ .ui-switch input {
402
+ position: absolute;
403
+ opacity: 0;
404
+ width: 0;
405
+ height: 0;
406
+ }
407
+ .ui-switch__track {
408
+ position: absolute;
409
+ inset: 0;
410
+ background: var(--surface-3);
411
+ border-radius: var(--radius-pill);
412
+ cursor: pointer;
413
+ transition: background var(--dur-med) var(--ease);
414
+ }
415
+ .ui-switch__track::before {
416
+ content: "";
417
+ position: absolute;
418
+ top: 3px;
419
+ left: 3px;
420
+ width: calc(var(--sw-h) - 6px);
421
+ height: calc(var(--sw-h) - 6px);
422
+ background: var(--control-knob);
423
+ border: 1px solid var(--border-strong);
424
+ box-sizing: border-box;
425
+ border-radius: 50%;
426
+ transition: transform var(--dur-med) var(--ease);
427
+ }
428
+ .ui-switch input:checked + .ui-switch__track {
429
+ background: var(--accent);
430
+ }
431
+ .ui-switch input:checked + .ui-switch__track::before {
432
+ transform: translateX(calc(var(--sw-w) - var(--sw-h)));
433
+ }
434
+ .ui-switch input:focus-visible + .ui-switch__track {
435
+ outline: 2px solid transparent;
436
+ box-shadow: var(--ring);
437
+ }
438
+ .ui-switch input:disabled + .ui-switch__track {
439
+ opacity: 0.5;
440
+ cursor: not-allowed;
441
+ }
442
+
443
+ /* src/Field.css */
444
+ .ui-field-number {
445
+ display: flex;
446
+ align-items: center;
447
+ position: relative;
448
+ }
449
+ .ui-field-number .ui-input {
450
+ padding-right: 4.5em;
451
+ }
452
+ .ui-field-number__unit {
453
+ position: absolute;
454
+ right: 15px;
455
+ color: var(--text);
456
+ pointer-events: none;
457
+ }
458
+ .ui-file {
459
+ display: flex;
460
+ flex-direction: column;
461
+ align-items: center;
462
+ gap: 8px;
463
+ padding: 24px;
464
+ border: 1px dashed var(--border);
465
+ border-radius: var(--radius-md);
466
+ background: var(--surface-2);
467
+ color: var(--text);
468
+ font-size: var(--text-base);
469
+ text-align: center;
470
+ overflow-wrap: anywhere;
471
+ }
472
+ .ui-file:hover {
473
+ border-color: var(--border-strong);
474
+ }
475
+ .ui-file:focus-within,
476
+ .ui-file.is-dragging {
477
+ border-color: var(--accent);
478
+ box-shadow: var(--ring);
479
+ outline: 2px solid transparent;
480
+ }
481
+ .ui-file.is-invalid {
482
+ border-color: var(--pink);
483
+ }
484
+ .ui-file__glyph {
485
+ display: flex;
486
+ padding: 12px;
487
+ border-radius: var(--radius-md);
488
+ background: var(--surface-3);
489
+ }
490
+ .ui-file__glyph svg {
491
+ width: 24px;
492
+ height: 24px;
493
+ }
494
+ .ui-file__choose {
495
+ position: relative;
496
+ display: inline-flex;
497
+ margin-top: 8px;
498
+ }
499
+ .ui-file__input {
500
+ position: absolute;
501
+ inset: 0;
502
+ width: 100%;
503
+ height: 100%;
504
+ opacity: 0;
505
+ cursor: pointer;
506
+ }
507
+ .ui-file.is-disabled {
508
+ background: var(--disabled-surface);
509
+ border-color: var(--disabled-border);
510
+ color: var(--disabled-ink);
511
+ }
512
+ .ui-file.is-disabled .ui-file__input {
513
+ cursor: not-allowed;
514
+ }
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ButtonHTMLAttributes, RefObject, MouseEvent, ElementType, ComponentPropsWithoutRef } from 'react';
2
+ import { ReactNode, ButtonHTMLAttributes, RefObject, MouseEvent, ElementType, ComponentPropsWithoutRef, InputHTMLAttributes, SelectHTMLAttributes, TextareaHTMLAttributes } from 'react';
3
3
 
4
4
  declare function Icon({ name }: {
5
5
  name: string;
@@ -506,7 +506,27 @@ type TimelineProps = Omit<ComponentPropsWithoutRef<'ol'>, 'children' | 'reversed
506
506
  };
507
507
  declare function Timeline({ events, className, ...rest }: TimelineProps): react.JSX.Element;
508
508
 
509
+ type FieldMessage = {
510
+ label: string;
511
+ hint?: string;
512
+ error?: string;
513
+ };
514
+ type Wiring = 'id' | 'aria-describedby' | 'aria-invalid';
515
+ type TextFieldProps = FieldMessage & Omit<InputHTMLAttributes<HTMLInputElement>, Wiring | 'type'> & {
516
+ type?: 'text' | 'number' | 'email';
517
+ unit?: string;
518
+ };
519
+ declare function TextField({ label, hint, error, required, type, unit, className, ...props }: TextFieldProps): react.JSX.Element;
520
+ type TextAreaProps = FieldMessage & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, Wiring>;
521
+ declare function TextArea({ label, hint, error, required, className, ...props }: TextAreaProps): react.JSX.Element;
522
+ type SelectFieldProps = FieldMessage & Omit<SelectHTMLAttributes<HTMLSelectElement>, Wiring>;
523
+ declare function SelectField({ label, hint, error, required, className, children, ...props }: SelectFieldProps): react.JSX.Element;
524
+ type FileFieldProps = FieldMessage & Omit<InputHTMLAttributes<HTMLInputElement>, Wiring | 'type' | 'multiple' | 'value' | 'defaultValue' | 'onChange'> & {
525
+ onFileChange?: (file: File | null) => void;
526
+ };
527
+ declare function FileField({ label, hint, error, required, disabled, onFileChange, className, ...props }: FileFieldProps): react.JSX.Element;
528
+
509
529
  declare const PAGE_SIZES: readonly number[];
510
530
  declare const DEFAULT_PAGE_SIZE: number;
511
531
 
512
- export { BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, type TabItem, type TableSort, Tabs, type TabsProps, Timeline, type TimelineEvent, type TimelineProps, sortTableRows };
532
+ export { BackLink, type BackLinkOwnProps, type BackLinkProps, Badge, BusyRegion, type BusyRegionProps, Button, type ButtonProps, Card, type Column, type CommandGroup, type CommandItem, CommandPalette, type CommandPaletteProps, Confirm, type ConfirmProps, DEFAULT_PAGE_SIZE, DataTable, type DataTableProps, DeltaValue, type DeltaValueProps, Denied, type DeniedProps, Drawer, type DrawerProps, DrawerSection, type DrawerSectionProps, Dropdown, type DropdownBadge, type DropdownEntry, type DropdownItem, type DropdownProps, type DropdownRowProps, type DropdownSearch, type DropdownSection, type DropdownSeparator, FileField, type FileFieldProps, type Filter, FilterBar, type FilterBarProps, Icon, KeyValueList, type KeyValueListProps, type KeyValueRow, Modal, type ModalProps, NumericValue, type NumericValueProps, PAGE_SIZES, Pagination, type PaginationProps, RowIdentity, type RowIdentityProps, type SegmentOption, Segmented, type SegmentedProps, SelectField, type SelectFieldProps, Skeleton, type SkeletonProps, SkeletonTable, type SkeletonTableProps, Snippet, type SnippetProps, StatBand, type StatBandProps, type StatDelta, type StatFigure, type StatTone, type StatVariant, type TabItem, type TableSort, Tabs, type TabsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, Timeline, type TimelineEvent, type TimelineProps, sortTableRows };
@@ -51,7 +51,7 @@ var cx = (...a) => a.filter(Boolean).join(" ");
51
51
  var Button = forwardRef(function Button2({
52
52
  variant = "secondary",
53
53
  size = "md",
54
- icon: icon4,
54
+ icon: icon5,
55
55
  iconRight,
56
56
  iconOnly,
57
57
  block,
@@ -107,7 +107,7 @@ var Button = forwardRef(function Button2({
107
107
  iconOnly && "ui-btn--icon"
108
108
  );
109
109
  const labelled = rest["aria-label"] != null || rest["aria-labelledby"] != null;
110
- const fallback = typeof children === "string" && children.trim() ? children.trim() : icon4;
110
+ const fallback = typeof children === "string" && children.trim() ? children.trim() : icon5;
111
111
  const named = iconOnly && !labelled && fallback ? { "aria-label": fallback, title: rest.title ?? fallback } : {};
112
112
  return /* @__PURE__ */ jsxs(
113
113
  "button",
@@ -141,7 +141,7 @@ var Button = forwardRef(function Button2({
141
141
  if (!blockKey(event)) onKeyUp?.(event);
142
142
  },
143
143
  children: [
144
- icon4 && /* @__PURE__ */ jsx2(Icon, { name: icon4 }),
144
+ icon5 && /* @__PURE__ */ jsx2(Icon, { name: icon5 }),
145
145
  !iconOnly && renderedChildren != null && /* @__PURE__ */ jsx2("span", { className: "ui-btn__label-slot", children: /* @__PURE__ */ jsx2("span", { className: "ui-btn__label", ref: labelRef, children: renderedChildren }) }),
146
146
  iconRight && /* @__PURE__ */ jsx2(Icon, { name: iconRight }),
147
147
  busy && /* @__PURE__ */ jsxs("span", { className: "ui-btn__dots", "aria-hidden": "true", children: [
@@ -1200,12 +1200,12 @@ function Denied({
1200
1200
  title = "You don\u2019t have access",
1201
1201
  sub,
1202
1202
  need,
1203
- icon: icon4 = "lock",
1203
+ icon: icon5 = "lock",
1204
1204
  className,
1205
1205
  children
1206
1206
  }) {
1207
1207
  return /* @__PURE__ */ jsxs10("div", { className: cx4("ui-denied", className), children: [
1208
- /* @__PURE__ */ jsx12("div", { className: "ui-denied__seal", children: /* @__PURE__ */ jsx12(Icon, { name: icon4 }) }),
1208
+ /* @__PURE__ */ jsx12("div", { className: "ui-denied__seal", children: /* @__PURE__ */ jsx12(Icon, { name: icon5 }) }),
1209
1209
  /* @__PURE__ */ jsx12("div", { className: "ui-denied__title", children: title }),
1210
1210
  sub && /* @__PURE__ */ jsx12("div", { className: "ui-denied__sub", children: sub }),
1211
1211
  need && /* @__PURE__ */ jsxs10("div", { className: "ui-denied__need", children: [
@@ -1892,6 +1892,140 @@ function Timeline({ events, className, ...rest }) {
1892
1892
  ] }, event.id)) });
1893
1893
  }
1894
1894
 
1895
+ // src/Field.tsx
1896
+ import { useEffect as useEffect7, useId as useId8, useRef as useRef12, useState as useState7 } from "react";
1897
+ import { esc, icon as icon4 } from "@apliteni/apliteni-ui";
1898
+ import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
1899
+ function Frame({ id, label, hint, error, required, children }) {
1900
+ return /* @__PURE__ */ jsxs18("div", { className: "ui-field", children: [
1901
+ /* @__PURE__ */ jsxs18("label", { className: "ui-field__label", htmlFor: id, children: [
1902
+ label,
1903
+ required && /* @__PURE__ */ jsx22("span", { className: "ui-field__req", "aria-hidden": "true", children: "*" })
1904
+ ] }),
1905
+ children,
1906
+ (error || hint) && /* @__PURE__ */ jsx22(
1907
+ "div",
1908
+ {
1909
+ id: `${id}-message`,
1910
+ className: error ? "ui-field__error" : "ui-field__hint",
1911
+ dangerouslySetInnerHTML: { __html: (error ? icon4("alert") : "") + esc(error || hint) }
1912
+ }
1913
+ )
1914
+ ] });
1915
+ }
1916
+ function wiring(id, hint, error) {
1917
+ return { id, "aria-describedby": error || hint ? `${id}-message` : void 0, "aria-invalid": error ? true : void 0 };
1918
+ }
1919
+ function TextField({ label, hint, error, required, type = "text", unit, className = "", ...props }) {
1920
+ const id = useId8();
1921
+ const hasUnit = type === "number" && !!unit;
1922
+ const describedBy = [error || hint ? `${id}-message` : "", hasUnit ? `${id}-unit` : ""].filter(Boolean).join(" ") || void 0;
1923
+ const control = /* @__PURE__ */ jsx22(
1924
+ "input",
1925
+ {
1926
+ ...props,
1927
+ ...wiring(id, hint, error),
1928
+ required,
1929
+ type,
1930
+ "aria-describedby": describedBy,
1931
+ inputMode: type === "number" ? "decimal" : props.inputMode,
1932
+ className: `ui-input${error ? " is-invalid" : ""} ${className}`
1933
+ }
1934
+ );
1935
+ return /* @__PURE__ */ jsx22(Frame, { ...{ id, label, hint, error, required }, children: hasUnit ? /* @__PURE__ */ jsxs18("div", { className: "ui-field-number", children: [
1936
+ control,
1937
+ /* @__PURE__ */ jsx22("span", { id: `${id}-unit`, className: "ui-field-number__unit", children: unit })
1938
+ ] }) : control });
1939
+ }
1940
+ function TextArea({ label, hint, error, required, className = "", ...props }) {
1941
+ const id = useId8();
1942
+ return /* @__PURE__ */ jsx22(Frame, { ...{ id, label, hint, error, required }, children: /* @__PURE__ */ jsx22(
1943
+ "textarea",
1944
+ {
1945
+ rows: 4,
1946
+ ...props,
1947
+ ...wiring(id, hint, error),
1948
+ required,
1949
+ className: `ui-textarea${error ? " is-invalid" : ""} ${className}`
1950
+ }
1951
+ ) });
1952
+ }
1953
+ function SelectField({ label, hint, error, required, className = "", children, ...props }) {
1954
+ const id = useId8();
1955
+ return /* @__PURE__ */ jsx22(Frame, { ...{ id, label, hint, error, required }, children: /* @__PURE__ */ jsx22(
1956
+ "select",
1957
+ {
1958
+ ...props,
1959
+ ...wiring(id, hint, error),
1960
+ required,
1961
+ className: `ui-select${error ? " is-invalid" : ""} ${className}`,
1962
+ children
1963
+ }
1964
+ ) });
1965
+ }
1966
+ function FileField({ label, hint, error, required, disabled, onFileChange, className = "", ...props }) {
1967
+ const id = useId8();
1968
+ const input = useRef12(null);
1969
+ const [file, setFile] = useState7(null);
1970
+ const [dragging, setDragging] = useState7(false);
1971
+ useEffect7(() => {
1972
+ const form = input.current?.form;
1973
+ const reset = () => {
1974
+ setFile(null);
1975
+ setDragging(false);
1976
+ };
1977
+ form?.addEventListener("reset", reset);
1978
+ return () => form?.removeEventListener("reset", reset);
1979
+ }, []);
1980
+ const update = (next) => {
1981
+ setFile(next);
1982
+ onFileChange?.(next);
1983
+ };
1984
+ return /* @__PURE__ */ jsx22(Frame, { ...{ id, label, hint, error, required }, children: /* @__PURE__ */ jsxs18(
1985
+ "div",
1986
+ {
1987
+ className: `ui-file${dragging && !disabled ? " is-dragging" : ""}${file ? " has-file" : ""}${error ? " is-invalid" : ""}${disabled ? " is-disabled" : ""}`,
1988
+ onDragOver: (event) => {
1989
+ event.preventDefault();
1990
+ if (!disabled) setDragging(true);
1991
+ },
1992
+ onDragLeave: (event) => {
1993
+ if (!event.currentTarget.contains(event.relatedTarget)) setDragging(false);
1994
+ },
1995
+ onDrop: (event) => {
1996
+ event.preventDefault();
1997
+ setDragging(false);
1998
+ if (disabled || !event.dataTransfer.files.length || !input.current) return;
1999
+ const transfer = new DataTransfer();
2000
+ transfer.items.add(event.dataTransfer.files[0]);
2001
+ input.current.files = transfer.files;
2002
+ update(transfer.files[0]);
2003
+ },
2004
+ children: [
2005
+ /* @__PURE__ */ jsx22("span", { className: "ui-file__glyph", children: /* @__PURE__ */ jsx22(Icon, { name: "upload" }) }),
2006
+ /* @__PURE__ */ jsx22("div", { "aria-live": "polite", children: file ? file.name : "Drop a file here" }),
2007
+ /* @__PURE__ */ jsx22("div", { children: "or choose one from your device" }),
2008
+ /* @__PURE__ */ jsxs18("span", { className: "ui-file__choose", children: [
2009
+ /* @__PURE__ */ jsx22("span", { className: "ui-btn ui-btn--secondary ui-btn--sm", "aria-hidden": "true", "aria-disabled": disabled || void 0, children: file ? "Replace file" : "Choose file" }),
2010
+ /* @__PURE__ */ jsx22(
2011
+ "input",
2012
+ {
2013
+ ...props,
2014
+ ...wiring(id, hint, error),
2015
+ ref: input,
2016
+ type: "file",
2017
+ required,
2018
+ disabled,
2019
+ className: `ui-file__input ${className}`,
2020
+ onChange: (event) => update(event.currentTarget.files?.[0] ?? null)
2021
+ }
2022
+ )
2023
+ ] })
2024
+ ]
2025
+ }
2026
+ ) });
2027
+ }
2028
+
1895
2029
  // src/index.ts
1896
2030
  var PAGE_SIZES = KIT_PAGE_SIZES;
1897
2031
  var DEFAULT_PAGE_SIZE3 = KIT_DEFAULT_PAGE_SIZE;
@@ -1910,6 +2044,7 @@ export {
1910
2044
  Drawer,
1911
2045
  DrawerSection,
1912
2046
  Dropdown,
2047
+ FileField,
1913
2048
  FilterBar,
1914
2049
  Icon,
1915
2050
  KeyValueList,
@@ -1919,11 +2054,14 @@ export {
1919
2054
  Pagination,
1920
2055
  RowIdentity,
1921
2056
  Segmented,
2057
+ SelectField,
1922
2058
  Skeleton,
1923
2059
  SkeletonTable,
1924
2060
  Snippet,
1925
2061
  StatBand,
1926
2062
  Tabs,
2063
+ TextArea,
2064
+ TextField,
1927
2065
  Timeline,
1928
2066
  sortTableRows
1929
2067
  };
@@ -63,7 +63,7 @@
63
63
  cursor: not-allowed;
64
64
  }
65
65
 
66
- .ui-input.is-invalid,
66
+ .ui-input.is-invalid, .ui-select.is-invalid,
67
67
  .ui-textarea.is-invalid { border-color: var(--pink); }
68
68
  /* Invalid fields keep the shared focus indicator; their border carries the error. */
69
69