@duro-app/ui 0.50.0 → 0.51.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": "@duro-app/ui",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -63,7 +63,7 @@
63
63
  },
64
64
  "dependencies": {
65
65
  "@tanstack/react-virtual": "^3.14.6",
66
- "@duro-app/tokens": "^0.50.0"
66
+ "@duro-app/tokens": "^0.51.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@babel/preset-typescript": "^7.28.0",
@@ -6,9 +6,10 @@ export const meta: ComponentMeta = {
6
6
  whenToUse: [
7
7
  'Bulk actions on table selections (delete, revoke, export)',
8
8
  'Multi-select workflows where actions apply to all selected items',
9
+ 'Selection-scoped verbs on a canvas/editor surface (set `bottomOffset` to clear any bottom chrome such as zoom controls)',
9
10
  ],
10
11
  whenNotToUse: [
11
- 'Single-item actions — use inline buttons or a Menu',
12
+ 'Single-item actions that have an inline home — use inline buttons or a Menu',
12
13
  'Persistent toolbars — use Inline with buttons',
13
14
  ],
14
15
  relatedTo: [
@@ -11,6 +11,7 @@ interface ActionBarStoryArgs {
11
11
  onClearSelection: () => void
12
12
  selectedItemCount?: number
13
13
  isEmphasized?: boolean
14
+ bottomOffset?: number
14
15
  }
15
16
 
16
17
  const meta: Meta<ActionBarStoryArgs> = {
@@ -24,6 +25,11 @@ const meta: Meta<ActionBarStoryArgs> = {
24
25
  control: 'boolean',
25
26
  description: 'Whether the action bar uses the emphasized (inverse) style',
26
27
  },
28
+ bottomOffset: {
29
+ control: 'number',
30
+ description:
31
+ 'Distance in px from the bottom of the viewport, for hosts whose bottom edge already carries chrome the bar must clear',
32
+ },
27
33
  },
28
34
  }
29
35
 
@@ -263,6 +269,35 @@ export const Hidden: Story = {
263
269
  render: () => <SelectableTable initialSelected={new Set()} />,
264
270
  }
265
271
 
272
+ export const BottomOffset: Story = {
273
+ render: () => (
274
+ <>
275
+ <Table.Root>
276
+ <Table.Header>
277
+ <Table.Row>
278
+ <Table.HeaderCell>Name</Table.HeaderCell>
279
+ </Table.Row>
280
+ </Table.Header>
281
+ <Table.Body>
282
+ <Table.Row>
283
+ <Table.Cell>Hero section</Table.Cell>
284
+ </Table.Row>
285
+ </Table.Body>
286
+ </Table.Root>
287
+ <ActionBar selectedItemCount={1} bottomOffset={72} onClearSelection={() => {}}>
288
+ <Button variant="secondary" size="small">
289
+ Resize
290
+ </Button>
291
+ </ActionBar>
292
+ </>
293
+ ),
294
+ play: async () => {
295
+ const toolbar = document.querySelector('[role="toolbar"]')
296
+ await expect(toolbar).toBeInTheDocument()
297
+ await expect(getComputedStyle(toolbar as Element).bottom).toBe('72px')
298
+ },
299
+ }
300
+
266
301
  export const AllVariants: Story = {
267
302
  render: () => (
268
303
  <html.div style={storyStyles.wrapper}>
@@ -14,6 +14,12 @@ export interface ActionBarProps {
14
14
  onClearSelection: () => void
15
15
  /** Whether the bar can be dismissed. When false, the close button is hidden. @default true */
16
16
  dismissible?: boolean
17
+ /**
18
+ * Distance in px between the bar and the bottom of the viewport, for hosts
19
+ * with their own bottom chrome (zoom controls, players) the bar must clear.
20
+ * Defaults to the spacing.lg token.
21
+ */
22
+ bottomOffset?: number
17
23
  /** Action buttons to display. */
18
24
  children: ReactNode
19
25
  }
@@ -24,6 +30,7 @@ function ActionBarContent({
24
30
  isEmphasized = false,
25
31
  onClearSelection,
26
32
  dismissible = true,
33
+ bottomOffset,
27
34
  children,
28
35
  }: ActionBarProps) {
29
36
  const countStr = selectedItemCount === 'all' ? 'All' : `${selectedItemCount}`
@@ -33,7 +40,11 @@ function ActionBarContent({
33
40
  <html.div
34
41
  role="toolbar"
35
42
  aria-label={label}
36
- style={[styles.overlay, isEmphasized && styles.overlayEmphasized]}
43
+ style={[
44
+ styles.overlay,
45
+ bottomOffset != null && styles.overlayOffset(bottomOffset),
46
+ isEmphasized && styles.overlayEmphasized,
47
+ ]}
37
48
  >
38
49
  <html.span style={[styles.selectedCount, isEmphasized && styles.selectedCountEmphasized]}>
39
50
  {label}
@@ -28,6 +28,9 @@ export const styles = css.create({
28
28
  zIndex: 50,
29
29
  maxWidth: 'calc(100vw - 48px)',
30
30
  },
31
+ overlayOffset: (bottom: number) => ({
32
+ bottom,
33
+ }),
31
34
  overlayEmphasized: {
32
35
  backgroundColor: colors.accent,
33
36
  borderColor: colors.accent,