@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.
- package/dist/{Table-gRiLI243.js → Table-ColfpgKV.js} +8 -4
- package/dist/Table-ColfpgKV.js.map +1 -0
- package/dist/components/FileTrigger/FileTrigger.d.ts +23 -0
- package/dist/components/FileTrigger/FileTrigger.d.ts.map +1 -0
- package/dist/components/FileTrigger/FileTrigger.meta.d.ts +3 -0
- package/dist/components/FileTrigger/FileTrigger.meta.d.ts.map +1 -0
- package/dist/components/FileTrigger/FileTrigger.stories.d.ts +9 -0
- package/dist/components/FileTrigger/FileTrigger.stories.d.ts.map +1 -0
- package/dist/components/FileTrigger/index.d.ts +3 -0
- package/dist/components/FileTrigger/index.d.ts.map +1 -0
- package/dist/components/FileTrigger/pick.d.ts +14 -0
- package/dist/components/FileTrigger/pick.d.ts.map +1 -0
- package/dist/components/Icon/Icon.d.ts +1 -1
- package/dist/components/Icon/Icon.d.ts.map +1 -1
- package/dist/components/Icon/Icon.stories.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1929 -1861
- package/dist/index.js.map +1 -1
- package/dist/table.js +1 -1
- package/package.json +2 -2
- package/src/components/FileTrigger/FileTrigger.meta.ts +24 -0
- package/src/components/FileTrigger/FileTrigger.stories.tsx +36 -0
- package/src/components/FileTrigger/FileTrigger.tsx +80 -0
- package/src/components/FileTrigger/index.ts +2 -0
- package/src/components/FileTrigger/pick.ts +21 -0
- package/src/components/Icon/Icon.stories.tsx +1 -0
- package/src/components/Icon/Icon.tsx +30 -0
- package/src/index.ts +5 -0
- 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-
|
|
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.
|
|
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.
|
|
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,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'
|