@duro-app/ui 5.10.0 → 5.11.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.
Files changed (31) hide show
  1. package/dist/{Table-gRiLI243.js → Table-ColfpgKV.js} +8 -4
  2. package/dist/Table-ColfpgKV.js.map +1 -0
  3. package/dist/components/FileTrigger/FileTrigger.d.ts +23 -0
  4. package/dist/components/FileTrigger/FileTrigger.d.ts.map +1 -0
  5. package/dist/components/FileTrigger/FileTrigger.meta.d.ts +3 -0
  6. package/dist/components/FileTrigger/FileTrigger.meta.d.ts.map +1 -0
  7. package/dist/components/FileTrigger/FileTrigger.stories.d.ts +9 -0
  8. package/dist/components/FileTrigger/FileTrigger.stories.d.ts.map +1 -0
  9. package/dist/components/FileTrigger/index.d.ts +3 -0
  10. package/dist/components/FileTrigger/index.d.ts.map +1 -0
  11. package/dist/components/FileTrigger/pick.d.ts +14 -0
  12. package/dist/components/FileTrigger/pick.d.ts.map +1 -0
  13. package/dist/components/Icon/Icon.d.ts +1 -1
  14. package/dist/components/Icon/Icon.d.ts.map +1 -1
  15. package/dist/components/Icon/Icon.stories.d.ts.map +1 -1
  16. package/dist/index.css +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1929 -1861
  20. package/dist/index.js.map +1 -1
  21. package/dist/table.js +1 -1
  22. package/package.json +2 -2
  23. package/src/components/FileTrigger/FileTrigger.meta.ts +24 -0
  24. package/src/components/FileTrigger/FileTrigger.stories.tsx +36 -0
  25. package/src/components/FileTrigger/FileTrigger.tsx +80 -0
  26. package/src/components/FileTrigger/index.ts +2 -0
  27. package/src/components/FileTrigger/pick.ts +21 -0
  28. package/src/components/Icon/Icon.stories.tsx +1 -0
  29. package/src/components/Icon/Icon.tsx +30 -0
  30. package/src/index.ts +5 -0
  31. package/dist/Table-gRiLI243.js.map +0 -1
package/dist/table.js CHANGED
@@ -1,4 +1,4 @@
1
- import { M as F, m as xt, R as rt, H as kt, n as pt, o as bt, p as ft, q as mt, b as ht, t as yt, r as St, v as Tt } from "./Table-gRiLI243.js";
1
+ import { M as F, m as xt, R as rt, H as kt, n as pt, o as bt, p as ft, q as mt, b as ht, t as yt, r as St, v as Tt } from "./Table-ColfpgKV.js";
2
2
  import { jsx as u, jsxs as q } from "react/jsx-runtime";
3
3
  import { c as H, m as p, d as b } from "./compiler-runtime-BuugS4D0.js";
4
4
  import { flexRender as E, useReactTable as it, getPaginationRowModel as Ct, getFilteredRowModel as vt, getSortedRowModel as at, getCoreRowModel as st } from "@tanstack/react-table";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duro-app/ui",
3
- "version": "5.10.0",
3
+ "version": "5.11.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -66,7 +66,7 @@
66
66
  },
67
67
  "dependencies": {
68
68
  "@tanstack/react-virtual": "^3.14.6",
69
- "@duro-app/tokens": "^5.10.0"
69
+ "@duro-app/tokens": "^5.11.0"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@babel/preset-typescript": "^7.28.0",
@@ -0,0 +1,24 @@
1
+ import type {ComponentMeta} from '../component-meta'
2
+
3
+ export const meta: ComponentMeta = {
4
+ description:
5
+ 'A Button that opens the OS file picker. Pass the chosen files to onSelect; call open() on its ref to open the picker from elsewhere.',
6
+ whenToUse: [
7
+ 'Uploading or attaching one or more files',
8
+ 'Opening the file picker from a custom control via the ref',
9
+ ],
10
+ whenNotToUse: [
11
+ 'Drag-and-drop zones — build on a drop target instead',
12
+ 'Triggering a non-file action — use Button',
13
+ ],
14
+ relatedTo: [
15
+ {
16
+ component: 'Button',
17
+ kind: 'composition',
18
+ relationship: 'FileTrigger renders a Button and forwards variant, size and disabled',
19
+ },
20
+ ],
21
+ example: `<FileTrigger accept="image/*" multiple onSelect={(files) => upload(files)}>
22
+ Choose images
23
+ </FileTrigger>`,
24
+ }
@@ -0,0 +1,36 @@
1
+ import type {Meta, StoryObj} from '@storybook/react'
2
+ import {expect, fn} from 'storybook/test'
3
+ import {FileTrigger} from './FileTrigger'
4
+
5
+ const meta: Meta<typeof FileTrigger> = {
6
+ title: 'Components/FileTrigger',
7
+ component: FileTrigger,
8
+ args: {onSelect: fn()},
9
+ argTypes: {
10
+ variant: {control: 'select', options: ['primary', 'secondary', 'link', 'danger']},
11
+ size: {control: 'select', options: ['default', 'small']},
12
+ multiple: {control: 'boolean'},
13
+ disabled: {control: 'boolean'},
14
+ },
15
+ }
16
+
17
+ export default meta
18
+ type Story = StoryObj<typeof FileTrigger>
19
+
20
+ export const Default: Story = {
21
+ args: {children: 'Choose file', variant: 'secondary'},
22
+ play: async ({args, canvas, userEvent, canvasElement}) => {
23
+ await expect(canvas.getByRole('button', {name: 'Choose file'})).toBeEnabled()
24
+ const input = canvasElement.querySelector<HTMLInputElement>('input[type=file]')!
25
+ await userEvent.upload(input, new File(['hi'], 'hi.txt', {type: 'text/plain'}))
26
+ await expect(args.onSelect).toHaveBeenCalledTimes(1)
27
+ },
28
+ }
29
+
30
+ export const Images: Story = {
31
+ args: {children: 'Choose images', accept: 'image/*', multiple: true},
32
+ }
33
+
34
+ export const Disabled: Story = {
35
+ args: {children: 'Choose file', disabled: true},
36
+ }
@@ -0,0 +1,80 @@
1
+ import {useEffect, useImperativeHandle, useRef} from 'react'
2
+ import type {ReactNode, Ref} from 'react'
3
+ import {css, html} from 'react-strict-dom'
4
+ import {Button, type ButtonSize, type ButtonVariant} from '../Button/Button'
5
+ import {openPicker, takeFiles} from './pick'
6
+
7
+ export interface FileTriggerHandle {
8
+ /** Open the OS file picker programmatically. */
9
+ open: () => void
10
+ }
11
+
12
+ export interface FileTriggerProps {
13
+ /** Accepted types, as the native `accept` attribute (e.g. `image/*,.pdf`). */
14
+ accept?: string
15
+ multiple?: boolean
16
+ /** Called with the chosen files. */
17
+ onSelect: (files: File[]) => void
18
+ variant?: ButtonVariant
19
+ size?: ButtonSize
20
+ disabled?: boolean
21
+ 'aria-label'?: string
22
+ /** Exposes `open()` so a caller can open the picker without the button. */
23
+ ref?: Ref<FileTriggerHandle>
24
+ children: ReactNode
25
+ }
26
+
27
+ // display:none, not visually hidden: the input is only ever opened by click(),
28
+ // which a hidden input still honours, and this keeps its "No file chosen" text
29
+ // out of the accessibility tree (axe flagged its contrast).
30
+ const styles = css.create({
31
+ input: {display: 'none'},
32
+ })
33
+
34
+ /** A Button that opens the OS file picker. Web only: native has no file input. */
35
+ export function FileTrigger({
36
+ accept,
37
+ multiple = false,
38
+ onSelect,
39
+ variant,
40
+ size,
41
+ disabled,
42
+ 'aria-label': ariaLabel,
43
+ ref,
44
+ children,
45
+ }: FileTriggerProps) {
46
+ const inputRef = useRef<HTMLInputElement>(null)
47
+ useImperativeHandle(ref, () => ({open: () => openPicker(inputRef.current)}), [])
48
+
49
+ // RSD's input props omit `accept`, so set the DOM attribute directly.
50
+ useEffect(() => {
51
+ const input = inputRef.current
52
+ if (!input) return
53
+ if (accept) input.setAttribute('accept', accept)
54
+ else input.removeAttribute('accept')
55
+ }, [accept])
56
+
57
+ return (
58
+ <>
59
+ <Button
60
+ variant={variant}
61
+ size={size}
62
+ disabled={disabled}
63
+ aria-label={ariaLabel}
64
+ onClick={() => openPicker(inputRef.current)}
65
+ >
66
+ {children}
67
+ </Button>
68
+ <html.input
69
+ ref={inputRef}
70
+ type="file"
71
+ multiple={multiple}
72
+ disabled={disabled}
73
+ tabIndex={-1}
74
+ aria-hidden={true}
75
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => takeFiles(e.target, onSelect)}
76
+ style={styles.input}
77
+ />
78
+ </>
79
+ )
80
+ }
@@ -0,0 +1,2 @@
1
+ export {FileTrigger} from './FileTrigger'
2
+ export type {FileTriggerProps, FileTriggerHandle} from './FileTrigger'
@@ -0,0 +1,21 @@
1
+ /** The slice of an <input type=file> the picker logic touches. */
2
+ export interface FileInputLike {
3
+ files: ArrayLike<File> | null
4
+ value: string
5
+ click: () => void
6
+ }
7
+
8
+ /** Open the native file picker. */
9
+ export function openPicker(input: Pick<FileInputLike, 'click'> | null) {
10
+ input?.click()
11
+ }
12
+
13
+ /**
14
+ * Hand the chosen files to `onSelect`, then reset the input so picking the
15
+ * same file again still fires a change event.
16
+ */
17
+ export function takeFiles(input: FileInputLike, onSelect: (files: File[]) => void) {
18
+ const files = input.files ? Array.from(input.files) : []
19
+ input.value = ''
20
+ if (files.length > 0) onSelect(files)
21
+ }
@@ -57,6 +57,7 @@ const groups: ReadonlyArray<{title: string; names: readonly IconName[]}> = [
57
57
  {title: 'Input / action', names: ['search', 'mic']},
58
58
  {title: 'Color mode', names: ['sun', 'moon', 'monitor', 'contrast']},
59
59
  {title: 'Device status', names: ['signal', 'battery']},
60
+ {title: 'Media', names: ['video', 'music', 'play', 'pause']},
60
61
  {
61
62
  title: 'Filled variants',
62
63
  names: [
@@ -48,6 +48,11 @@ export type IconName =
48
48
  // Device status glyphs
49
49
  | 'signal'
50
50
  | 'battery'
51
+ // Media glyphs
52
+ | 'video'
53
+ | 'music'
54
+ | 'play'
55
+ | 'pause'
51
56
  // Filled variants (solid shape with cutout symbol)
52
57
  | 'info-circle-filled'
53
58
  | 'alert-triangle-filled'
@@ -333,6 +338,31 @@ const strokeIcons: Partial<Record<IconName, ReactNode>> = {
333
338
  <line x1="22" y1="11" x2="22" y2="13" />
334
339
  </>
335
340
  ),
341
+ // ---- media ----
342
+ // Camera body with a side lens — video file or clip.
343
+ video: (
344
+ <>
345
+ <rect x="2" y="6" width="14" height="12" rx="2" />
346
+ <path d="M16 10l6-3v10l-6-3z" />
347
+ </>
348
+ ),
349
+ // Beamed note — audio file or track.
350
+ music: (
351
+ <>
352
+ <path d="M9 18V5l12-2v13" />
353
+ <circle cx="6" cy="18" r="3" />
354
+ <circle cx="18" cy="16" r="3" />
355
+ </>
356
+ ),
357
+ // Right-pointing triangle — start playback.
358
+ play: <path d="M6 4l14 8-14 8z" />,
359
+ // Two vertical bars — pause playback.
360
+ pause: (
361
+ <>
362
+ <rect x="6" y="4" width="4" height="16" />
363
+ <rect x="14" y="4" width="4" height="16" />
364
+ </>
365
+ ),
336
366
  }
337
367
 
338
368
  /**
package/src/index.ts CHANGED
@@ -20,6 +20,11 @@ export {Icon, type IconName} from './components/Icon'
20
20
  export {Badge, type BadgeVariant, type BadgeSize} from './components/Badge/Badge'
21
21
  export {Breadcrumb} from './components/Breadcrumb/Breadcrumb'
22
22
  export {Button, type ButtonVariant, type ButtonSize} from './components/Button/Button'
23
+ export {
24
+ FileTrigger,
25
+ type FileTriggerProps,
26
+ type FileTriggerHandle,
27
+ } from './components/FileTrigger/FileTrigger'
23
28
  export type {ControlSize} from './shared/types'
24
29
  export {ButtonGroup, type ButtonGroupProps} from './components/ButtonGroup/ButtonGroup'
25
30
  export {Callout, type CalloutVariant} from './components/Callout/Callout'