@duro-app/ui 0.21.0 → 0.23.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/components/ActionBar/ActionBar.stories.d.ts.map +1 -1
- package/dist/components/Alert/Alert.meta.d.ts.map +1 -1
- package/dist/components/Callout/Callout.meta.d.ts.map +1 -1
- package/dist/components/Card/Card.meta.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts +22 -0
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -0
- package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts +3 -0
- package/dist/components/CheckboxGroup/CheckboxGroup.meta.d.ts.map +1 -0
- package/dist/components/CheckboxGroup/CheckboxGroup.stories.d.ts +9 -0
- package/dist/components/CheckboxGroup/CheckboxGroup.stories.d.ts.map +1 -0
- package/dist/components/CheckboxGroup/styles.css.d.ts +12 -0
- package/dist/components/CheckboxGroup/styles.css.d.ts.map +1 -0
- package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.meta.d.ts.map +1 -1
- package/dist/components/DetailPanel/DetailPanel.stories.d.ts.map +1 -1
- package/dist/components/DetailPanel/styles.css.d.ts +4 -0
- package/dist/components/DetailPanel/styles.css.d.ts.map +1 -1
- package/dist/components/Dialog/Dialog.meta.d.ts.map +1 -1
- package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
- package/dist/components/EmptyState/EmptyState.meta.d.ts.map +1 -1
- package/dist/components/Field/Field.stories.d.ts.map +1 -1
- package/dist/components/InputGroup/InputGroup.meta.d.ts.map +1 -1
- package/dist/components/List/List.d.ts +46 -0
- package/dist/components/List/List.d.ts.map +1 -0
- package/dist/components/List/List.meta.d.ts +3 -0
- package/dist/components/List/List.meta.d.ts.map +1 -0
- package/dist/components/List/List.stories.d.ts +9 -0
- package/dist/components/List/List.stories.d.ts.map +1 -0
- package/dist/components/List/styles.css.d.ts +77 -0
- package/dist/components/List/styles.css.d.ts.map +1 -0
- package/dist/components/Panel/Panel.d.ts.map +1 -1
- package/dist/components/Panel/Panel.meta.d.ts.map +1 -1
- package/dist/components/Panel/Panel.stories.d.ts.map +1 -1
- package/dist/components/Panel/styles.css.d.ts +4 -0
- package/dist/components/Panel/styles.css.d.ts.map +1 -1
- package/dist/components/RadioGroup/RadioGroup.d.ts +22 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -0
- package/dist/components/RadioGroup/RadioGroup.meta.d.ts +3 -0
- package/dist/components/RadioGroup/RadioGroup.meta.d.ts.map +1 -0
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts +9 -0
- package/dist/components/RadioGroup/RadioGroup.stories.d.ts.map +1 -0
- package/dist/components/RadioGroup/styles.css.d.ts +60 -0
- package/dist/components/RadioGroup/styles.css.d.ts.map +1 -0
- package/dist/components/StatusIcon/StatusIcon.meta.d.ts.map +1 -1
- package/dist/components/Table/Table.d.ts +4 -3
- package/dist/components/Table/Table.d.ts.map +1 -1
- package/dist/components/Table/Table.stories.d.ts.map +1 -1
- package/dist/components/Table/styles.css.d.ts +1 -1
- package/dist/components/Tabs/Tabs.stories.d.ts.map +1 -1
- package/dist/components/ThemeProvider/ThemeProvider.meta.d.ts.map +1 -1
- package/dist/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1771 -1386
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/ActionBar/ActionBar.stories.tsx +10 -0
- package/src/components/Alert/Alert.meta.ts +4 -1
- package/src/components/Callout/Callout.meta.ts +4 -1
- package/src/components/Card/Card.meta.ts +4 -1
- package/src/components/Checkbox/styles.css.ts +1 -1
- package/src/components/CheckboxGroup/CheckboxGroup.meta.ts +25 -0
- package/src/components/CheckboxGroup/CheckboxGroup.stories.tsx +88 -0
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +105 -0
- package/src/components/CheckboxGroup/styles.css.ts +13 -0
- package/src/components/DetailPanel/DetailPanel.meta.ts +4 -1
- package/src/components/DetailPanel/DetailPanel.stories.tsx +54 -27
- package/src/components/DetailPanel/DetailPanel.tsx +3 -16
- package/src/components/DetailPanel/styles.css.ts +5 -0
- package/src/components/Dialog/Dialog.meta.ts +8 -2
- package/src/components/Drawer/Drawer.meta.ts +4 -1
- package/src/components/EmptyState/EmptyState.meta.ts +1 -4
- package/src/components/Field/Field.stories.tsx +6 -0
- package/src/components/Icon/Icon.meta.ts +5 -5
- package/src/components/InputGroup/InputGroup.meta.ts +4 -1
- package/src/components/List/List.meta.ts +39 -0
- package/src/components/List/List.stories.tsx +200 -0
- package/src/components/List/List.tsx +118 -0
- package/src/components/List/styles.css.ts +99 -0
- package/src/components/Panel/Panel.meta.ts +12 -3
- package/src/components/Panel/Panel.stories.tsx +17 -10
- package/src/components/Panel/Panel.tsx +2 -10
- package/src/components/Panel/styles.css.ts +5 -0
- package/src/components/RadioGroup/RadioGroup.meta.ts +25 -0
- package/src/components/RadioGroup/RadioGroup.stories.tsx +83 -0
- package/src/components/RadioGroup/RadioGroup.tsx +113 -0
- package/src/components/RadioGroup/styles.css.ts +66 -0
- package/src/components/StatusIcon/StatusIcon.meta.ts +1 -4
- package/src/components/Table/Table.stories.tsx +25 -9
- package/src/components/Table/Table.tsx +31 -15
- package/src/components/Table/styles.css.ts +2 -2
- package/src/components/Tabs/Tabs.stories.tsx +7 -0
- package/src/components/ThemeProvider/ThemeProvider.meta.ts +1 -3
- package/src/components/Tooltip/Tooltip.stories.tsx +15 -0
- package/src/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duro-app/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@duro-app/tokens": "^0.
|
|
55
|
+
"@duro-app/tokens": "^0.23.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@babel/preset-typescript": "^7.28.0",
|
|
@@ -13,6 +13,16 @@ interface ActionBarStoryArgs {
|
|
|
13
13
|
|
|
14
14
|
const meta: Meta<ActionBarStoryArgs> = {
|
|
15
15
|
title: 'Components/ActionBar',
|
|
16
|
+
argTypes: {
|
|
17
|
+
selectedItemCount: {
|
|
18
|
+
control: 'number',
|
|
19
|
+
description: 'Number of selected items displayed in the action bar',
|
|
20
|
+
},
|
|
21
|
+
isEmphasized: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
description: 'Whether the action bar uses the emphasized (inverse) style',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
export default meta
|
|
@@ -14,7 +14,10 @@ export const meta: ComponentMeta = {
|
|
|
14
14
|
'Form field errors — use Field.Error',
|
|
15
15
|
],
|
|
16
16
|
relatedTo: [
|
|
17
|
-
{
|
|
17
|
+
{
|
|
18
|
+
component: 'Callout',
|
|
19
|
+
relationship: 'Callout for larger block-level messages; Alert for compact inline',
|
|
20
|
+
},
|
|
18
21
|
],
|
|
19
22
|
example: `<Stack gap="sm">
|
|
20
23
|
<Alert variant="success">Settings saved successfully.</Alert>
|
|
@@ -14,7 +14,10 @@ export const meta: ComponentMeta = {
|
|
|
14
14
|
'Toast notifications — use a toast system',
|
|
15
15
|
],
|
|
16
16
|
relatedTo: [
|
|
17
|
-
{
|
|
17
|
+
{
|
|
18
|
+
component: 'Alert',
|
|
19
|
+
relationship: 'Alert for compact inline; Callout for block-level prominent',
|
|
20
|
+
},
|
|
18
21
|
],
|
|
19
22
|
example: `<Callout variant="warning">
|
|
20
23
|
Your subscription expires in 3 days. Renew now to avoid service interruption.
|
|
@@ -14,7 +14,10 @@ export const meta: ComponentMeta = {
|
|
|
14
14
|
'Simple spacing — use Stack or Inline',
|
|
15
15
|
],
|
|
16
16
|
relatedTo: [
|
|
17
|
-
{
|
|
17
|
+
{
|
|
18
|
+
component: 'Panel',
|
|
19
|
+
relationship: 'Panel for structural Header/Body/Footer; Card for visual container',
|
|
20
|
+
},
|
|
18
21
|
],
|
|
19
22
|
example: `<Card variant="elevated" size="md">
|
|
20
23
|
<Stack gap="md">
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'Checkbox group for multi-select from a list of options. Wraps the existing Checkbox component with shared state management.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'Selecting multiple options from a small set',
|
|
8
|
+
'Permission or feature toggles where several can be active',
|
|
9
|
+
],
|
|
10
|
+
whenNotToUse: [
|
|
11
|
+
'Single selection — use RadioGroup instead',
|
|
12
|
+
'Binary on/off — use a single Checkbox or Switch',
|
|
13
|
+
'Many options (10+) — consider a filterable list or Select with multi-select',
|
|
14
|
+
],
|
|
15
|
+
relatedTo: [
|
|
16
|
+
{component: 'RadioGroup', relationship: 'RadioGroup for single-select; CheckboxGroup for multi-select'},
|
|
17
|
+
{component: 'Checkbox', relationship: 'CheckboxGroup.Item wraps Checkbox internally'},
|
|
18
|
+
{component: 'ToggleGroup', relationship: 'ToggleGroup for toolbar-style multi-select'},
|
|
19
|
+
],
|
|
20
|
+
example: `<CheckboxGroup.Root defaultValue={['email']} onValueChange={(v) => console.log(v)}>
|
|
21
|
+
<CheckboxGroup.Item value="email">Email</CheckboxGroup.Item>
|
|
22
|
+
<CheckboxGroup.Item value="sms">SMS</CheckboxGroup.Item>
|
|
23
|
+
<CheckboxGroup.Item value="push">Push notification</CheckboxGroup.Item>
|
|
24
|
+
</CheckboxGroup.Root>`,
|
|
25
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {useState} from 'react'
|
|
2
|
+
import type {Meta, StoryObj} from '@storybook/react'
|
|
3
|
+
import {expect, fn} from 'storybook/test'
|
|
4
|
+
import {css, html} from 'react-strict-dom'
|
|
5
|
+
import {CheckboxGroup} from './CheckboxGroup'
|
|
6
|
+
|
|
7
|
+
const meta: Meta = {
|
|
8
|
+
title: 'Components/CheckboxGroup',
|
|
9
|
+
argTypes: {
|
|
10
|
+
orientation: {control: 'select', options: ['horizontal', 'vertical']},
|
|
11
|
+
disabled: {control: 'boolean'},
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
type Story = StoryObj
|
|
17
|
+
|
|
18
|
+
export const Default: Story = {
|
|
19
|
+
render: () => (
|
|
20
|
+
<CheckboxGroup.Root defaultValue={['email']}>
|
|
21
|
+
<CheckboxGroup.Item value="email">Email notifications</CheckboxGroup.Item>
|
|
22
|
+
<CheckboxGroup.Item value="sms">SMS notifications</CheckboxGroup.Item>
|
|
23
|
+
<CheckboxGroup.Item value="push">Push notifications</CheckboxGroup.Item>
|
|
24
|
+
</CheckboxGroup.Root>
|
|
25
|
+
),
|
|
26
|
+
play: async ({canvas}) => {
|
|
27
|
+
const checkboxes = canvas.getAllByRole('checkbox')
|
|
28
|
+
await expect(checkboxes.length).toBe(3)
|
|
29
|
+
await expect(checkboxes[0]).toBeChecked()
|
|
30
|
+
await expect(checkboxes[1]).not.toBeChecked()
|
|
31
|
+
await expect(checkboxes[2]).not.toBeChecked()
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Horizontal: Story = {
|
|
36
|
+
render: () => (
|
|
37
|
+
<CheckboxGroup.Root defaultValue={['react']} orientation="horizontal">
|
|
38
|
+
<CheckboxGroup.Item value="react">React</CheckboxGroup.Item>
|
|
39
|
+
<CheckboxGroup.Item value="vue">Vue</CheckboxGroup.Item>
|
|
40
|
+
<CheckboxGroup.Item value="angular">Angular</CheckboxGroup.Item>
|
|
41
|
+
</CheckboxGroup.Root>
|
|
42
|
+
),
|
|
43
|
+
play: async ({canvas}) => {
|
|
44
|
+
const group = canvas.getByRole('group')
|
|
45
|
+
await expect(group).toHaveAttribute('aria-orientation', 'horizontal')
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const Disabled: Story = {
|
|
50
|
+
render: () => (
|
|
51
|
+
<CheckboxGroup.Root defaultValue={['a']} disabled>
|
|
52
|
+
<CheckboxGroup.Item value="a">Option A</CheckboxGroup.Item>
|
|
53
|
+
<CheckboxGroup.Item value="b">Option B</CheckboxGroup.Item>
|
|
54
|
+
</CheckboxGroup.Root>
|
|
55
|
+
),
|
|
56
|
+
play: async ({canvas}) => {
|
|
57
|
+
const checkboxes = canvas.getAllByRole('checkbox')
|
|
58
|
+
await expect(checkboxes[0]).toBeDisabled()
|
|
59
|
+
await expect(checkboxes[1]).toBeDisabled()
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const Controlled: Story = {
|
|
64
|
+
render: () => {
|
|
65
|
+
const [value, setValue] = useState(['read'])
|
|
66
|
+
return (
|
|
67
|
+
<html.div>
|
|
68
|
+
<CheckboxGroup.Root value={value} onValueChange={setValue}>
|
|
69
|
+
<CheckboxGroup.Item value="read">Read</CheckboxGroup.Item>
|
|
70
|
+
<CheckboxGroup.Item value="write">Write</CheckboxGroup.Item>
|
|
71
|
+
<CheckboxGroup.Item value="execute">Execute</CheckboxGroup.Item>
|
|
72
|
+
</CheckboxGroup.Root>
|
|
73
|
+
</html.div>
|
|
74
|
+
)
|
|
75
|
+
},
|
|
76
|
+
play: async ({canvas, userEvent}) => {
|
|
77
|
+
const checkboxes = canvas.getAllByRole('checkbox')
|
|
78
|
+
await expect(checkboxes[0]).toBeChecked()
|
|
79
|
+
|
|
80
|
+
await userEvent.click(checkboxes[1])
|
|
81
|
+
await expect(checkboxes[0]).toBeChecked()
|
|
82
|
+
await expect(checkboxes[1]).toBeChecked()
|
|
83
|
+
|
|
84
|
+
await userEvent.click(checkboxes[0])
|
|
85
|
+
await expect(checkboxes[0]).not.toBeChecked()
|
|
86
|
+
await expect(checkboxes[1]).toBeChecked()
|
|
87
|
+
},
|
|
88
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {type ReactNode, createContext, useContext, useCallback} from 'react'
|
|
2
|
+
import {html} from 'react-strict-dom'
|
|
3
|
+
import {useControllableValue} from '../../hooks/useControllableValue'
|
|
4
|
+
import {Checkbox} from '../Checkbox/Checkbox'
|
|
5
|
+
import {styles} from './styles.css'
|
|
6
|
+
|
|
7
|
+
// --- Context ---
|
|
8
|
+
|
|
9
|
+
interface CheckboxGroupContextValue {
|
|
10
|
+
value: string[]
|
|
11
|
+
onToggle: (itemValue: string, checked: boolean) => void
|
|
12
|
+
disabled: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const CheckboxGroupContext = createContext<CheckboxGroupContextValue | null>(null)
|
|
16
|
+
|
|
17
|
+
function useCheckboxGroup() {
|
|
18
|
+
const ctx = useContext(CheckboxGroupContext)
|
|
19
|
+
if (!ctx) {
|
|
20
|
+
throw new Error('CheckboxGroup.Item must be used within CheckboxGroup.Root')
|
|
21
|
+
}
|
|
22
|
+
return ctx
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// --- Root ---
|
|
26
|
+
|
|
27
|
+
interface RootProps {
|
|
28
|
+
value?: string[]
|
|
29
|
+
defaultValue?: string[]
|
|
30
|
+
onValueChange?: (value: string[]) => void
|
|
31
|
+
orientation?: 'horizontal' | 'vertical'
|
|
32
|
+
disabled?: boolean
|
|
33
|
+
children: ReactNode
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Root({
|
|
37
|
+
value: controlledValue,
|
|
38
|
+
defaultValue = [],
|
|
39
|
+
onValueChange,
|
|
40
|
+
orientation = 'vertical',
|
|
41
|
+
disabled = false,
|
|
42
|
+
children,
|
|
43
|
+
}: RootProps) {
|
|
44
|
+
const [value, setValue] = useControllableValue(controlledValue, defaultValue, onValueChange)
|
|
45
|
+
|
|
46
|
+
const onToggle = useCallback(
|
|
47
|
+
(itemValue: string, checked: boolean) => {
|
|
48
|
+
if (disabled) return
|
|
49
|
+
const next = checked
|
|
50
|
+
? [...value, itemValue]
|
|
51
|
+
: value.filter((v) => v !== itemValue)
|
|
52
|
+
setValue(next)
|
|
53
|
+
},
|
|
54
|
+
[disabled, value, setValue],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<CheckboxGroupContext.Provider value={{value, onToggle, disabled}}>
|
|
59
|
+
<html.div
|
|
60
|
+
role="group"
|
|
61
|
+
aria-orientation={orientation}
|
|
62
|
+
style={[styles.root, orientation === 'horizontal' && styles.rootHorizontal]}
|
|
63
|
+
>
|
|
64
|
+
{children}
|
|
65
|
+
</html.div>
|
|
66
|
+
</CheckboxGroupContext.Provider>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Item ---
|
|
71
|
+
|
|
72
|
+
interface ItemProps {
|
|
73
|
+
value: string
|
|
74
|
+
disabled?: boolean
|
|
75
|
+
children: ReactNode
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Item({value, disabled: itemDisabled = false, children}: ItemProps) {
|
|
79
|
+
const {value: groupValue, onToggle, disabled: groupDisabled} = useCheckboxGroup()
|
|
80
|
+
const isChecked = groupValue.includes(value)
|
|
81
|
+
const isDisabled = groupDisabled || itemDisabled
|
|
82
|
+
|
|
83
|
+
const handleChange = useCallback(
|
|
84
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
85
|
+
onToggle(value, e.target.checked)
|
|
86
|
+
},
|
|
87
|
+
[value, onToggle],
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Checkbox
|
|
92
|
+
value={value}
|
|
93
|
+
checked={isChecked}
|
|
94
|
+
disabled={isDisabled}
|
|
95
|
+
onChange={handleChange}
|
|
96
|
+
>
|
|
97
|
+
{children}
|
|
98
|
+
</Checkbox>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const CheckboxGroup = {
|
|
103
|
+
Root,
|
|
104
|
+
Item,
|
|
105
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {css} from 'react-strict-dom'
|
|
2
|
+
import {spacing} from '@duro-app/tokens/tokens/spacing.css'
|
|
3
|
+
|
|
4
|
+
export const styles = css.create({
|
|
5
|
+
root: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
gap: spacing.sm,
|
|
9
|
+
},
|
|
10
|
+
rootHorizontal: {
|
|
11
|
+
flexDirection: 'row',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
@@ -18,7 +18,10 @@ export const meta: ComponentMeta = {
|
|
|
18
18
|
optional: ['Title', 'Footer', 'Close'],
|
|
19
19
|
},
|
|
20
20
|
relatedTo: [
|
|
21
|
-
{
|
|
21
|
+
{
|
|
22
|
+
component: 'Drawer',
|
|
23
|
+
relationship: 'Drawer is modal with backdrop; DetailPanel is non-modal and in-flow',
|
|
24
|
+
},
|
|
22
25
|
{component: 'Panel', relationship: 'DetailPanel uses Panel structure internally'},
|
|
23
26
|
{component: 'Table', relationship: 'Common pattern: Table + DetailPanel for master-detail'},
|
|
24
27
|
],
|
|
@@ -2,6 +2,7 @@ import type {Meta, StoryObj} from '@storybook/react'
|
|
|
2
2
|
import {useState} from 'react'
|
|
3
3
|
import {css, html} from 'react-strict-dom'
|
|
4
4
|
import {DetailPanel} from './DetailPanel'
|
|
5
|
+
import {Panel} from '../Panel/Panel'
|
|
5
6
|
import {Button} from '../Button/Button'
|
|
6
7
|
import {Text} from '../Text/Text'
|
|
7
8
|
import {Heading} from '../Heading/Heading'
|
|
@@ -56,9 +57,8 @@ function DefaultDemo() {
|
|
|
56
57
|
</DetailPanel.Header>
|
|
57
58
|
<DetailPanel.Body>
|
|
58
59
|
<Text>
|
|
59
|
-
This is a non-modal detail panel. The content behind it
|
|
60
|
-
|
|
61
|
-
interact with the main area.
|
|
60
|
+
This is a non-modal detail panel. The content behind it remains interactive — you can
|
|
61
|
+
still click, scroll, and interact with the main area.
|
|
62
62
|
</Text>
|
|
63
63
|
</DetailPanel.Body>
|
|
64
64
|
<DetailPanel.Footer>
|
|
@@ -137,9 +137,15 @@ function TableSelectionDemo() {
|
|
|
137
137
|
<Heading level={3}>Users</Heading>
|
|
138
138
|
<html.div>
|
|
139
139
|
<html.div style={tableStyles.headerRow}>
|
|
140
|
-
<html.span style={tableStyles.cellText}
|
|
141
|
-
|
|
142
|
-
|
|
140
|
+
<html.span style={tableStyles.cellText}>
|
|
141
|
+
<Text variant="label">Name</Text>
|
|
142
|
+
</html.span>
|
|
143
|
+
<html.span style={tableStyles.cellText}>
|
|
144
|
+
<Text variant="label">Role</Text>
|
|
145
|
+
</html.span>
|
|
146
|
+
<html.span style={tableStyles.cellText}>
|
|
147
|
+
<Text variant="label">Status</Text>
|
|
148
|
+
</html.span>
|
|
143
149
|
</html.div>
|
|
144
150
|
{users.map((user) => (
|
|
145
151
|
<html.div
|
|
@@ -147,9 +153,15 @@ function TableSelectionDemo() {
|
|
|
147
153
|
style={[tableStyles.row, selectedId === user.id && tableStyles.rowSelected]}
|
|
148
154
|
onClick={() => setSelectedId(selectedId === user.id ? null : user.id)}
|
|
149
155
|
>
|
|
150
|
-
<html.span style={tableStyles.cellText}
|
|
151
|
-
|
|
152
|
-
|
|
156
|
+
<html.span style={tableStyles.cellText}>
|
|
157
|
+
<Text>{user.name}</Text>
|
|
158
|
+
</html.span>
|
|
159
|
+
<html.span style={tableStyles.cellText}>
|
|
160
|
+
<Text>{user.role}</Text>
|
|
161
|
+
</html.span>
|
|
162
|
+
<html.span style={tableStyles.cellText}>
|
|
163
|
+
<Text>{user.status}</Text>
|
|
164
|
+
</html.span>
|
|
153
165
|
</html.div>
|
|
154
166
|
))}
|
|
155
167
|
</html.div>
|
|
@@ -170,22 +182,37 @@ function TableSelectionDemo() {
|
|
|
170
182
|
<DetailPanel.Body>
|
|
171
183
|
{selectedUser && (
|
|
172
184
|
<Stack gap="md">
|
|
173
|
-
<
|
|
174
|
-
<
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
185
|
+
<Panel.Root>
|
|
186
|
+
<Panel.Header>
|
|
187
|
+
<Text variant="label">Contact</Text>
|
|
188
|
+
</Panel.Header>
|
|
189
|
+
<Panel.Body>
|
|
190
|
+
<Stack gap="xs">
|
|
191
|
+
<Text variant="caption">Email</Text>
|
|
192
|
+
<Text variant="bodySm">{selectedUser.email}</Text>
|
|
193
|
+
</Stack>
|
|
194
|
+
</Panel.Body>
|
|
195
|
+
</Panel.Root>
|
|
196
|
+
|
|
197
|
+
<Panel.Root>
|
|
198
|
+
<Panel.Header>
|
|
199
|
+
<Text variant="label">Role & Status</Text>
|
|
200
|
+
</Panel.Header>
|
|
201
|
+
<Panel.Body>
|
|
202
|
+
<Stack gap="sm">
|
|
203
|
+
<Inline gap="sm" align="center">
|
|
204
|
+
<Text variant="caption">Role</Text>
|
|
205
|
+
<Text variant="bodySm">{selectedUser.role}</Text>
|
|
206
|
+
</Inline>
|
|
207
|
+
<Inline gap="sm" align="center">
|
|
208
|
+
<Text variant="caption">Status</Text>
|
|
209
|
+
<Badge variant={selectedUser.status === 'Active' ? 'success' : 'default'}>
|
|
210
|
+
{selectedUser.status}
|
|
211
|
+
</Badge>
|
|
212
|
+
</Inline>
|
|
213
|
+
</Stack>
|
|
214
|
+
</Panel.Body>
|
|
215
|
+
</Panel.Root>
|
|
189
216
|
</Stack>
|
|
190
217
|
)}
|
|
191
218
|
</DetailPanel.Body>
|
|
@@ -225,8 +252,8 @@ function MediumSizeDemo() {
|
|
|
225
252
|
</DetailPanel.Header>
|
|
226
253
|
<DetailPanel.Body>
|
|
227
254
|
<Text>
|
|
228
|
-
This panel is 480px wide, providing more room for complex
|
|
229
|
-
|
|
255
|
+
This panel is 480px wide, providing more room for complex content like forms or
|
|
256
|
+
detailed information.
|
|
230
257
|
</Text>
|
|
231
258
|
</DetailPanel.Body>
|
|
232
259
|
</DetailPanel.Content>
|
|
@@ -194,12 +194,7 @@ function Content({children, size = 'sm', label}: ContentProps) {
|
|
|
194
194
|
if (!present) return null
|
|
195
195
|
|
|
196
196
|
return (
|
|
197
|
-
<html.div
|
|
198
|
-
style={[
|
|
199
|
-
styles.wrapper,
|
|
200
|
-
closing ? wrapperCloseMap[size] : wrapperOpenMap[size],
|
|
201
|
-
]}
|
|
202
|
-
>
|
|
197
|
+
<html.div style={[styles.wrapper, closing ? wrapperCloseMap[size] : wrapperOpenMap[size]]}>
|
|
203
198
|
<html.div
|
|
204
199
|
ref={panelRef}
|
|
205
200
|
role="complementary"
|
|
@@ -250,11 +245,7 @@ interface BodyProps {
|
|
|
250
245
|
}
|
|
251
246
|
|
|
252
247
|
function Body({children, padded = true}: BodyProps) {
|
|
253
|
-
return
|
|
254
|
-
<html.div style={[styles.body, padded && styles.bodyPadded]}>
|
|
255
|
-
{children}
|
|
256
|
-
</html.div>
|
|
257
|
-
)
|
|
248
|
+
return <html.div style={[styles.body, padded && styles.bodyPadded]}>{children}</html.div>
|
|
258
249
|
}
|
|
259
250
|
|
|
260
251
|
// --- Footer ---
|
|
@@ -282,11 +273,7 @@ function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
|
|
|
282
273
|
}
|
|
283
274
|
|
|
284
275
|
return (
|
|
285
|
-
<html.button
|
|
286
|
-
onClick={requestClose}
|
|
287
|
-
aria-label={ariaLabel}
|
|
288
|
-
style={styles.closeButton}
|
|
289
|
-
>
|
|
276
|
+
<html.button onClick={requestClose} aria-label={ariaLabel} style={styles.closeButton}>
|
|
290
277
|
<svg width={16} height={16} viewBox="0 0 16 16" fill="none">
|
|
291
278
|
<path
|
|
292
279
|
d="M4 4l8 8M12 4l-8 8"
|
|
@@ -137,6 +137,11 @@ export const styles = css.create({
|
|
|
137
137
|
body: {
|
|
138
138
|
flex: 1,
|
|
139
139
|
overflowY: 'auto',
|
|
140
|
+
// Minimal inset prevents child borders from touching panel structural borders
|
|
141
|
+
paddingTop: spacing.sm,
|
|
142
|
+
paddingBottom: spacing.sm,
|
|
143
|
+
paddingLeft: spacing.sm,
|
|
144
|
+
paddingRight: spacing.sm,
|
|
140
145
|
},
|
|
141
146
|
bodyPadded: {
|
|
142
147
|
paddingTop: spacing.lg,
|
|
@@ -20,8 +20,14 @@ export const meta: ComponentMeta = {
|
|
|
20
20
|
optional: ['Trigger', 'Header', 'Title', 'Description', 'Body', 'Footer', 'Close'],
|
|
21
21
|
},
|
|
22
22
|
relatedTo: [
|
|
23
|
-
{
|
|
24
|
-
|
|
23
|
+
{
|
|
24
|
+
component: 'Drawer',
|
|
25
|
+
relationship: 'Drawer slides from an edge; Dialog is centered with scale animation',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
component: 'DetailPanel',
|
|
29
|
+
relationship: 'DetailPanel is non-modal and in-flow; Dialog is modal with backdrop',
|
|
30
|
+
},
|
|
25
31
|
],
|
|
26
32
|
example: `<Dialog.Root open={open} onOpenChange={setOpen}>
|
|
27
33
|
<Dialog.Trigger>
|
|
@@ -21,7 +21,10 @@ export const meta: ComponentMeta = {
|
|
|
21
21
|
},
|
|
22
22
|
relatedTo: [
|
|
23
23
|
{component: 'Dialog', relationship: 'Dialog is centered modal; Drawer slides from an edge'},
|
|
24
|
-
{
|
|
24
|
+
{
|
|
25
|
+
component: 'DetailPanel',
|
|
26
|
+
relationship: 'DetailPanel is non-modal and in-flow; Drawer is modal with backdrop',
|
|
27
|
+
},
|
|
25
28
|
],
|
|
26
29
|
example: `<Drawer.Root anchor="right" open={open} onOpenChange={setOpen}>
|
|
27
30
|
<Drawer.Trigger>
|
|
@@ -8,10 +8,7 @@ export const meta: ComponentMeta = {
|
|
|
8
8
|
'Search results with no matches',
|
|
9
9
|
'First-time use before any data exists',
|
|
10
10
|
],
|
|
11
|
-
whenNotToUse: [
|
|
12
|
-
'Loading states — use Spinner',
|
|
13
|
-
'Error states — use Alert or Callout',
|
|
14
|
-
],
|
|
11
|
+
whenNotToUse: ['Loading states — use Spinner', 'Error states — use Alert or Callout'],
|
|
15
12
|
example: `<Card>
|
|
16
13
|
<EmptyState
|
|
17
14
|
icon={<Icon name="info-circle" size={48} />}
|
|
@@ -8,12 +8,12 @@ export const meta: ComponentMeta = {
|
|
|
8
8
|
'Status indicators alongside text',
|
|
9
9
|
'Navigation or action indicators',
|
|
10
10
|
],
|
|
11
|
-
whenNotToUse: [
|
|
12
|
-
'Decorative images — use img elements',
|
|
13
|
-
'Complex illustrations — use custom SVGs',
|
|
14
|
-
],
|
|
11
|
+
whenNotToUse: ['Decorative images — use img elements', 'Complex illustrations — use custom SVGs'],
|
|
15
12
|
relatedTo: [
|
|
16
|
-
{
|
|
13
|
+
{
|
|
14
|
+
component: 'StatusIcon',
|
|
15
|
+
relationship: 'StatusIcon wraps Icon with a colored background circle',
|
|
16
|
+
},
|
|
17
17
|
],
|
|
18
18
|
example: `<Inline gap="sm" align="center">
|
|
19
19
|
<Icon name="check-circle" size={20} />
|
|
@@ -18,7 +18,10 @@ export const meta: ComponentMeta = {
|
|
|
18
18
|
},
|
|
19
19
|
relatedTo: [
|
|
20
20
|
{component: 'Input', relationship: 'InputGroup enhances Input with addons'},
|
|
21
|
-
{
|
|
21
|
+
{
|
|
22
|
+
component: 'Field',
|
|
23
|
+
relationship: 'Field provides label/error; InputGroup provides visual addons',
|
|
24
|
+
},
|
|
22
25
|
],
|
|
23
26
|
example: `<InputGroup.Root>
|
|
24
27
|
<InputGroup.Addon>$</InputGroup.Addon>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type {ComponentMeta} from '../component-meta'
|
|
2
|
+
|
|
3
|
+
export const meta: ComponentMeta = {
|
|
4
|
+
description:
|
|
5
|
+
'Vertical list of interactive items. Ideal for narrow spaces (detail panels, sidebars) where Table would truncate. Supports selection, descriptions, and action slots.',
|
|
6
|
+
whenToUse: [
|
|
7
|
+
'Displaying items in a narrow container (< 480px)',
|
|
8
|
+
'Detail panels, drawers, and sidebars',
|
|
9
|
+
'Lists needing selection (single or multiple) with compact layout',
|
|
10
|
+
],
|
|
11
|
+
whenNotToUse: [
|
|
12
|
+
'Tabular data with many columns — use Table',
|
|
13
|
+
'Navigation menus — use SideNav or Menu',
|
|
14
|
+
'Simple text lists without interaction — use Stack with Text',
|
|
15
|
+
],
|
|
16
|
+
anatomy: {
|
|
17
|
+
required: ['Root', 'Item', 'Content', 'Text'],
|
|
18
|
+
optional: ['Description', 'Actions', 'Empty'],
|
|
19
|
+
},
|
|
20
|
+
relatedTo: [
|
|
21
|
+
{component: 'Table', relationship: 'Table for wide multi-column data; List for narrow compact items'},
|
|
22
|
+
{component: 'DetailPanel', relationship: 'List fits naturally inside DetailPanel.Body'},
|
|
23
|
+
{component: 'Menu', relationship: 'Menu for action menus; List for data display with selection'},
|
|
24
|
+
],
|
|
25
|
+
example: `<List.Root selectionMode="multiple" aria-label="Certificates">
|
|
26
|
+
<List.Item selected onClick={toggle}>
|
|
27
|
+
<Checkbox checked />
|
|
28
|
+
<List.Content>
|
|
29
|
+
<List.Text>web-server-prod-2024.pem</List.Text>
|
|
30
|
+
<List.Description>Issued: Jan 15, 2024 · Expires: Jan 15, 2025</List.Description>
|
|
31
|
+
</List.Content>
|
|
32
|
+
<List.Actions>
|
|
33
|
+
<Badge variant="success">Active</Badge>
|
|
34
|
+
<Button variant="danger" size="small">Revoke</Button>
|
|
35
|
+
</List.Actions>
|
|
36
|
+
</List.Item>
|
|
37
|
+
<List.Empty>No certificates found.</List.Empty>
|
|
38
|
+
</List.Root>`,
|
|
39
|
+
}
|