@zylem/ui 0.8.8 → 0.8.11

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 (81) hide show
  1. package/dist/chunk-V47CGFWX.js +1 -0
  2. package/dist/index.css +1 -1
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.js +1 -1
  5. package/dist/styles.css +1 -1
  6. package/dist/styles.js +1 -1
  7. package/package.json +22 -11
  8. package/src/components/Accordion/Accordion.tsx +10 -1
  9. package/src/components/AddTile/AddTile.css.ts +95 -0
  10. package/src/components/AddTile/AddTile.tsx +45 -0
  11. package/src/components/BuildLabel/BuildLabel.css.ts +31 -0
  12. package/src/components/BuildLabel/BuildLabel.tsx +37 -0
  13. package/src/components/BuildLabel/mount-build-label.ts +77 -0
  14. package/src/components/BuildLabel/resolve-build-version.ts +47 -0
  15. package/src/components/Callout/Callout.css.ts +45 -0
  16. package/src/components/Callout/Callout.tsx +32 -0
  17. package/src/components/Card/Card.css.ts +18 -0
  18. package/src/components/Card/Card.tsx +25 -2
  19. package/src/components/ColorPalette/ColorPalette.css.ts +51 -0
  20. package/src/components/ColorPalette/ColorPalette.tsx +78 -0
  21. package/src/components/Dialog/Dialog.css.ts +69 -2
  22. package/src/components/Dialog/Dialog.tsx +35 -7
  23. package/src/components/DropdownMenu/DropdownMenu.css.ts +22 -0
  24. package/src/components/DropdownMenu/DropdownMenu.tsx +67 -20
  25. package/src/components/EmptyState/EmptyState.css.ts +9 -0
  26. package/src/components/EmptyState/EmptyState.tsx +3 -1
  27. package/src/components/FrameStrip/FrameStrip.css.ts +138 -0
  28. package/src/components/FrameStrip/FrameStrip.tsx +146 -0
  29. package/src/components/Menubar/Menubar.css.ts +52 -0
  30. package/src/components/Menubar/Menubar.tsx +77 -0
  31. package/src/components/Mixer/Knob.tsx +188 -0
  32. package/src/components/Mixer/Mixer.css.ts +246 -0
  33. package/src/components/Mixer/MixerStrip.tsx +150 -0
  34. package/src/components/NumberField/NumberField.tsx +5 -1
  35. package/src/components/Panel/Panel.tsx +6 -5
  36. package/src/components/PianoRoll/PianoRoll.css.ts +224 -0
  37. package/src/components/PianoRoll/PianoRoll.tsx +353 -0
  38. package/src/components/PianoRoll/piano-roll-math.ts +89 -0
  39. package/src/components/PixelGrid/PixelGrid.css.ts +30 -0
  40. package/src/components/PixelGrid/PixelGrid.tsx +48 -0
  41. package/src/components/PlaybackControls/PlaybackControls.css.ts +53 -0
  42. package/src/components/PlaybackControls/PlaybackControls.tsx +80 -0
  43. package/src/components/Property/Property.css.ts +1 -1
  44. package/src/components/Property/Property.tsx +3 -0
  45. package/src/components/Sidebar/Sidebar.css.ts +8 -0
  46. package/src/components/Sidebar/Sidebar.tsx +10 -2
  47. package/src/components/Slider/Slider.tsx +6 -0
  48. package/src/components/SpriteSizePicker/SpriteSizePicker.css.ts +103 -0
  49. package/src/components/SpriteSizePicker/SpriteSizePicker.tsx +172 -0
  50. package/src/components/SpriteSizePicker/snap-size.ts +28 -0
  51. package/src/components/Tabs/Tabs.css.ts +48 -0
  52. package/src/components/Tabs/Tabs.tsx +41 -3
  53. package/src/components/TextField/TextField.tsx +49 -5
  54. package/src/components/TimelineLanes/TimelineLanes.css.ts +365 -0
  55. package/src/components/TimelineLanes/TimelineLanes.tsx +509 -0
  56. package/src/components/TimelineLanes/lanes-math.ts +182 -0
  57. package/src/components/TimelineTrack/TimelineTrack.css.ts +163 -0
  58. package/src/components/TimelineTrack/TimelineTrack.tsx +90 -0
  59. package/src/components/TimelineTrack/timeline-math.ts +43 -0
  60. package/src/components/Toast/Toast.css.ts +48 -0
  61. package/src/components/Toast/Toast.tsx +37 -0
  62. package/src/components/ToolButton/ToolButton.tsx +92 -0
  63. package/src/components/ToolGroup/ToolGroup.css.ts +35 -0
  64. package/src/components/ToolGroup/ToolGroup.tsx +29 -0
  65. package/src/components/ToolboxButton/ToolboxButton.css.ts +131 -0
  66. package/src/components/ToolboxButton/ToolboxButton.tsx +88 -0
  67. package/src/components/TrackHeader/TrackHeader.css.ts +162 -0
  68. package/src/components/TrackHeader/TrackHeader.tsx +145 -0
  69. package/src/components/TransportBar/TransportBar.css.ts +65 -0
  70. package/src/components/TransportBar/TransportBar.tsx +154 -0
  71. package/src/components/Waveform/Waveform.css.ts +77 -0
  72. package/src/components/Waveform/Waveform.tsx +195 -0
  73. package/src/components/Waveform/peaks.ts +49 -0
  74. package/src/components/index.ts +122 -0
  75. package/src/global/accordion.css.ts +23 -0
  76. package/src/global/hyperglass-base.css.ts +9 -6
  77. package/src/styles.ts +23 -0
  78. package/src/theme.css.ts +15 -0
  79. package/vite/collect-zylem-versions.ts +82 -0
  80. package/vite/index.ts +30 -0
  81. package/dist/chunk-3TP2SNNW.js +0 -1
@@ -0,0 +1,78 @@
1
+ import { For, Show } from 'solid-js';
2
+ import { AddTile } from '../AddTile/AddTile';
3
+
4
+ /** Sentinel colour for the eraser / transparent swatch. */
5
+ export const TRANSPARENT = 'transparent';
6
+
7
+ export interface SwatchProps {
8
+ /** Any CSS colour, or `'transparent'` for the checkered swatch. */
9
+ color: string;
10
+ selected?: boolean;
11
+ onClick?: (event: MouseEvent) => void;
12
+ /** Accessible name; defaults to the colour string. */
13
+ label?: string;
14
+ disabled?: boolean;
15
+ class?: string;
16
+ }
17
+
18
+ /** One palette colour: a small glass-edged square, ringed when selected. */
19
+ export function Swatch(props: SwatchProps) {
20
+ const transparent = () => props.color === TRANSPARENT;
21
+ return (
22
+ <button
23
+ type="button"
24
+ class={props.class ? `zylem-swatch ${props.class}` : 'zylem-swatch'}
25
+ style={transparent() ? undefined : { '--zylem-swatch-color': props.color }}
26
+ data-transparent={transparent() ? '' : undefined}
27
+ data-selected={props.selected ? '' : undefined}
28
+ aria-pressed={props.selected ? 'true' : 'false'}
29
+ aria-label={props.label ?? (transparent() ? 'Transparent' : props.color)}
30
+ title={props.label ?? (transparent() ? 'Transparent' : props.color)}
31
+ disabled={props.disabled}
32
+ onClick={props.onClick}
33
+ />
34
+ );
35
+ }
36
+
37
+ export interface ColorPaletteProps {
38
+ colors: readonly string[];
39
+ /** Currently selected colour (compare with `colors` entries or `'transparent'`). */
40
+ value?: string;
41
+ onSelect?: (color: string) => void;
42
+ /** When provided, an add tile follows the swatches. */
43
+ onAdd?: () => void;
44
+ /** Lead with the transparent swatch. Defaults to true. */
45
+ includeTransparent?: boolean;
46
+ class?: string;
47
+ }
48
+
49
+ /** Row of swatches with an optional transparent lead and add tile. */
50
+ export function ColorPalette(props: ColorPaletteProps) {
51
+ return (
52
+ <div
53
+ role="listbox"
54
+ aria-label="Palette"
55
+ class={props.class ? `zylem-palette ${props.class}` : 'zylem-palette'}
56
+ >
57
+ <Show when={props.includeTransparent ?? true}>
58
+ <Swatch
59
+ color={TRANSPARENT}
60
+ selected={props.value === TRANSPARENT}
61
+ onClick={() => props.onSelect?.(TRANSPARENT)}
62
+ />
63
+ </Show>
64
+ <For each={props.colors}>
65
+ {(color) => (
66
+ <Swatch
67
+ color={color}
68
+ selected={props.value === color}
69
+ onClick={() => props.onSelect?.(color)}
70
+ />
71
+ )}
72
+ </For>
73
+ <Show when={props.onAdd}>
74
+ <AddTile label="Add color" onClick={() => props.onAdd?.()} />
75
+ </Show>
76
+ </div>
77
+ );
78
+ }
@@ -32,8 +32,12 @@ globalStyle('.zylem-dialog-positioner', {
32
32
  });
33
33
 
34
34
  globalStyle('.zylem-dialog-content', {
35
+ display: 'flex',
36
+ flexDirection: 'column',
35
37
  width: '100%',
36
38
  maxWidth: '440px',
39
+ maxHeight: '100%',
40
+ minHeight: 0,
37
41
  borderRadius: vars.radii.window,
38
42
  border: '1px solid rgba(146, 205, 255, 0.54)',
39
43
  background: vars.material.glassPanelDark,
@@ -48,19 +52,41 @@ globalStyle('.zylem-dialog-content', {
48
52
  },
49
53
  });
50
54
 
55
+ globalStyle('.zylem-dialog-content[data-size="lg"]', {
56
+ maxWidth: '760px',
57
+ });
58
+
59
+ // Fullscreen fills the positioner (viewport minus its padding). The body takes
60
+ // whatever height the header and footer leave, so browser-style content can
61
+ // hand scrolling to its own columns.
62
+ globalStyle('.zylem-dialog-content[data-size="fullscreen"]', {
63
+ maxWidth: 'none',
64
+ height: '100%',
65
+ borderRadius: vars.radii.card,
66
+ });
67
+
51
68
  globalStyle('.zylem-dialog-header', {
69
+ flex: 'none',
52
70
  minHeight: '40px',
53
71
  display: 'flex',
54
72
  alignItems: 'center',
55
73
  justifyContent: 'space-between',
56
- gap: vars.spacing.sm,
57
- padding: `0 ${vars.spacing.md}`,
74
+ gap: vars.spacing.md,
75
+ padding: `${vars.spacing.xs} ${vars.spacing.md}`,
58
76
  background: vars.material.glossyBar,
59
77
  borderBottom: '1px solid rgba(5, 18, 32, 0.85)',
60
78
  boxShadow:
61
79
  'inset 0 1px 0 rgba(255,255,255,0.62), inset 0 -1px 0 rgba(255,255,255,0.08)',
62
80
  });
63
81
 
82
+ globalStyle('.zylem-dialog-titles', {
83
+ display: 'flex',
84
+ flexDirection: 'column',
85
+ gap: '2px',
86
+ flex: '1 1 auto',
87
+ minWidth: 0,
88
+ });
89
+
64
90
  globalStyle('.zylem-dialog-title', {
65
91
  margin: 0,
66
92
  fontSize: '13px',
@@ -71,7 +97,26 @@ globalStyle('.zylem-dialog-title', {
71
97
  textShadow: '0 1px 1px rgba(0,0,0,0.65)',
72
98
  });
73
99
 
100
+ globalStyle('.zylem-dialog-subtitle', {
101
+ margin: 0,
102
+ fontSize: `calc(${vars.typography.fontSize} - 2px)`,
103
+ fontWeight: 500,
104
+ color: 'rgba(232, 244, 255, 0.78)',
105
+ fontFamily: vars.typography.fontFamily,
106
+ textShadow: '0 1px 1px rgba(0,0,0,0.5)',
107
+ });
108
+
109
+ globalStyle('.zylem-dialog-header-extra', {
110
+ display: 'flex',
111
+ alignItems: 'center',
112
+ gap: vars.spacing.sm,
113
+ flex: 'none',
114
+ });
115
+
74
116
  globalStyle('.zylem-dialog-body', {
117
+ flex: '1 1 auto',
118
+ minHeight: 0,
119
+ overflow: 'auto',
75
120
  padding: vars.spacing.lg,
76
121
  color: vars.colors.text,
77
122
  fontFamily: vars.typography.fontFamily,
@@ -79,9 +124,31 @@ globalStyle('.zylem-dialog-body', {
79
124
  lineHeight: 1.45,
80
125
  });
81
126
 
127
+ // In fullscreen the body and its layer scope become flex columns so content
128
+ // can lay out against the dialog's height (and own its scrolling).
129
+ globalStyle('.zylem-dialog-content[data-size="fullscreen"] .zylem-dialog-body', {
130
+ display: 'flex',
131
+ flexDirection: 'column',
132
+ });
133
+
134
+ globalStyle('.zylem-dialog-content[data-size="fullscreen"] .zylem-dialog-scope', {
135
+ display: 'flex',
136
+ flexDirection: 'column',
137
+ flex: '1 1 auto',
138
+ minHeight: 0,
139
+ });
140
+
82
141
  globalStyle('.zylem-dialog-footer', {
142
+ flex: 'none',
83
143
  display: 'flex',
144
+ alignItems: 'center',
84
145
  justifyContent: 'flex-end',
85
146
  gap: vars.spacing.sm,
86
147
  padding: `0 ${vars.spacing.lg} ${vars.spacing.lg}`,
87
148
  });
149
+
150
+ globalStyle('.zylem-dialog-content[data-size="fullscreen"] .zylem-dialog-footer', {
151
+ padding: `${vars.spacing.sm} ${vars.spacing.lg}`,
152
+ borderTop: '1px solid rgba(97, 166, 232, 0.28)',
153
+ background: 'rgba(4, 12, 20, 0.6)',
154
+ });
@@ -5,13 +5,29 @@ import { createDismissGuard, LayerScope, useLayer } from '../../layers';
5
5
  import { Button } from '../Button/Button';
6
6
  import { WindowControls } from '../WindowControls/WindowControls';
7
7
 
8
+ /**
9
+ * Card width. `md` is the default 440px card, `lg` a 760px card for forms
10
+ * with two columns, and `fullscreen` fills the viewport (minus a margin) with
11
+ * the body taking the remaining height — the shell for browser-style pickers.
12
+ */
13
+ export type DialogSize = 'md' | 'lg' | 'fullscreen';
14
+
8
15
  export interface DialogProps {
9
16
  open?: boolean;
10
17
  onOpenChange?: (open: boolean) => void;
11
18
  title?: JSX.Element;
19
+ /** Secondary line under the title. */
20
+ subtitle?: JSX.Element;
21
+ /** Content between the titles and the close control, e.g. a search field. */
22
+ headerExtra?: JSX.Element;
23
+ size?: DialogSize;
12
24
  children: JSX.Element;
13
25
  /** Optional footer content (e.g. action buttons). */
14
26
  footer?: JSX.Element;
27
+ /** Extra class on the card. */
28
+ class?: string;
29
+ /** Extra class on the body, e.g. to hand scrolling to the content's columns. */
30
+ bodyClass?: string;
15
31
  }
16
32
 
17
33
  /**
@@ -43,19 +59,31 @@ export function Dialog(props: DialogProps) {
43
59
  <div class="zylem-dialog-positioner">
44
60
  <KDialog.Content
45
61
  ref={dismiss.ref}
46
- class="zylem-dialog-content"
62
+ class={props.class ? `zylem-dialog-content ${props.class}` : 'zylem-dialog-content'}
63
+ data-size={props.size}
47
64
  onInteractOutside={dismiss.onInteractOutside}
48
65
  >
49
66
  <div class="zylem-dialog-header">
50
- <Show when={props.title}>
51
- <KDialog.Title class="zylem-dialog-title">
52
- {props.title}
53
- </KDialog.Title>
67
+ <Show when={props.title || props.subtitle}>
68
+ <div class="zylem-dialog-titles">
69
+ <Show when={props.title}>
70
+ <KDialog.Title class="zylem-dialog-title">{props.title}</KDialog.Title>
71
+ </Show>
72
+ <Show when={props.subtitle}>
73
+ <p class="zylem-dialog-subtitle">{props.subtitle}</p>
74
+ </Show>
75
+ </div>
76
+ </Show>
77
+ <Show when={props.headerExtra}>
78
+ <div class="zylem-dialog-header-extra">{props.headerExtra}</div>
54
79
  </Show>
55
80
  <WindowControls onClose={() => props.onOpenChange?.(false)} />
56
81
  </div>
57
- <KDialog.Description as="div" class="zylem-dialog-body">
58
- <LayerScope>{props.children}</LayerScope>
82
+ <KDialog.Description
83
+ as="div"
84
+ class={props.bodyClass ? `zylem-dialog-body ${props.bodyClass}` : 'zylem-dialog-body'}
85
+ >
86
+ <LayerScope class="zylem-dialog-scope">{props.children}</LayerScope>
59
87
  </KDialog.Description>
60
88
  <Show when={props.footer}>
61
89
  <div class="zylem-dialog-footer">{props.footer}</div>
@@ -61,6 +61,28 @@ globalStyle('.zylem-dropdown-item--danger[data-highlighted]', {
61
61
  color: '#FFD9D3',
62
62
  });
63
63
 
64
+ globalStyle('.zylem-dropdown-label', {
65
+ flex: '1 1 auto',
66
+ minWidth: 0,
67
+ });
68
+
69
+ // Check column, reserved on every row of a menu that has a checkable item.
70
+ globalStyle('.zylem-dropdown-check', {
71
+ display: 'inline-flex',
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ flex: 'none',
75
+ width: '14px',
76
+ height: '14px',
77
+ marginRight: `calc(${vars.spacing.xs} * -1)`,
78
+ });
79
+
80
+ globalStyle('.zylem-dropdown-check svg', {
81
+ width: '13px',
82
+ height: '13px',
83
+ stroke: 'currentColor',
84
+ });
85
+
64
86
  globalStyle('.zylem-dropdown-shortcut', {
65
87
  fontSize: `calc(${vars.typography.fontSize} - 3px)`,
66
88
  color: vars.colors.textSecondary,
@@ -1,4 +1,5 @@
1
1
  import { DropdownMenu as KDropdownMenu } from '@kobalte/core';
2
+ import Check from 'lucide-solid/icons/check';
2
3
  import { For, Show } from 'solid-js';
3
4
  import type { JSX } from 'solid-js';
4
5
  import { createDismissGuard, useLayer } from '../../layers';
@@ -10,6 +11,12 @@ export interface DropdownMenuItemConfig {
10
11
  shortcut?: string;
11
12
  disabled?: boolean;
12
13
  danger?: boolean;
14
+ /**
15
+ * Render as a checkable item showing this state. Selecting it still calls
16
+ * `onSelect`; the owner decides what the new state is (a plain toggle, or
17
+ * one-of-many the way a stage picker works).
18
+ */
19
+ checked?: boolean;
13
20
  /** Render a separator line above this item. */
14
21
  separatorAbove?: boolean;
15
22
  }
@@ -24,40 +31,80 @@ export interface DropdownMenuProps {
24
31
  }
25
32
 
26
33
  /**
27
- * HyperGlass dropdown menu: floating glass sheet with highlight rows.
34
+ * The rows of a menu. Shared by `DropdownMenu` and `Menubar`, whose Kobalte
35
+ * parts are the same underlying menu primitives, so it may be rendered inside
36
+ * either one's content.
28
37
  *
29
- * Portals into the layer container for its tree at the `menu` tier, so it opens
30
- * over panels and dialogs and stays styled inside a shadow root.
38
+ * A menu with any checkable row reserves the check column on every row so
39
+ * labels line up.
31
40
  */
32
- export function DropdownMenu(props: DropdownMenuProps) {
33
- const layer = useLayer('menu');
34
- const dismiss = createDismissGuard();
41
+ export function MenuItems(props: { items: DropdownMenuItemConfig[] }) {
42
+ const hasChecks = () => props.items.some((item) => item.checked !== undefined);
35
43
 
36
- const items = () => (
44
+ const body = (item: DropdownMenuItemConfig) => (
45
+ <>
46
+ <Show when={hasChecks()}>
47
+ <span class="zylem-dropdown-check" aria-hidden="true">
48
+ <Show when={item.checked !== undefined}>
49
+ <KDropdownMenu.ItemIndicator>
50
+ <Check />
51
+ </KDropdownMenu.ItemIndicator>
52
+ </Show>
53
+ </span>
54
+ </Show>
55
+ <span class="zylem-dropdown-label">{item.label}</span>
56
+ <Show when={item.shortcut}>
57
+ <span class="zylem-dropdown-shortcut">{item.shortcut}</span>
58
+ </Show>
59
+ </>
60
+ );
61
+
62
+ const itemClass = (item: DropdownMenuItemConfig) =>
63
+ item.danger ? 'zylem-dropdown-item zylem-dropdown-item--danger' : 'zylem-dropdown-item';
64
+
65
+ return (
37
66
  <For each={props.items}>
38
67
  {(item) => (
39
68
  <>
40
69
  <Show when={item.separatorAbove}>
41
70
  <KDropdownMenu.Separator class="zylem-dropdown-separator" />
42
71
  </Show>
43
- <KDropdownMenu.Item
44
- class={
45
- item.danger
46
- ? 'zylem-dropdown-item zylem-dropdown-item--danger'
47
- : 'zylem-dropdown-item'
72
+ <Show
73
+ when={item.checked !== undefined}
74
+ fallback={
75
+ <KDropdownMenu.Item
76
+ class={itemClass(item)}
77
+ disabled={item.disabled}
78
+ onSelect={item.onSelect}
79
+ >
80
+ {body(item)}
81
+ </KDropdownMenu.Item>
48
82
  }
49
- disabled={item.disabled}
50
- onSelect={item.onSelect}
51
83
  >
52
- <span>{item.label}</span>
53
- <Show when={item.shortcut}>
54
- <span class="zylem-dropdown-shortcut">{item.shortcut}</span>
55
- </Show>
56
- </KDropdownMenu.Item>
84
+ <KDropdownMenu.CheckboxItem
85
+ class={itemClass(item)}
86
+ checked={item.checked}
87
+ disabled={item.disabled}
88
+ onSelect={item.onSelect}
89
+ >
90
+ {body(item)}
91
+ </KDropdownMenu.CheckboxItem>
92
+ </Show>
57
93
  </>
58
94
  )}
59
95
  </For>
60
96
  );
97
+ }
98
+
99
+ /**
100
+ * HyperGlass dropdown menu: floating glass sheet with highlight rows.
101
+ *
102
+ * Portals into the layer container for its tree at the `menu` tier, so it opens
103
+ * over panels and dialogs and stays styled inside a shadow root.
104
+ */
105
+ export function DropdownMenu(props: DropdownMenuProps) {
106
+ const layer = useLayer('menu');
107
+ const dismiss = createDismissGuard();
61
108
 
62
109
  return (
63
110
  <KDropdownMenu.Root placement={props.placement ?? 'bottom-start'}>
@@ -72,7 +119,7 @@ export function DropdownMenu(props: DropdownMenuProps) {
72
119
  class="zylem-dropdown-content"
73
120
  onInteractOutside={dismiss.onInteractOutside}
74
121
  >
75
- {items()}
122
+ <MenuItems items={props.items} />
76
123
  </KDropdownMenu.Content>
77
124
  </KDropdownMenu.Portal>
78
125
  )}
@@ -27,3 +27,12 @@ globalStyle('.zylem-empty-state-message', {
27
27
  lineHeight: 1.45,
28
28
  maxWidth: '360px',
29
29
  });
30
+
31
+ // One or several actions side by side, wrapping on narrow hosts.
32
+ globalStyle('.zylem-empty-state-actions', {
33
+ display: 'flex',
34
+ flexWrap: 'wrap',
35
+ justifyContent: 'center',
36
+ gap: vars.spacing.sm,
37
+ marginTop: vars.spacing.xs,
38
+ });
@@ -26,7 +26,9 @@ export function EmptyState(props: EmptyStateProps) {
26
26
  <Show when={props.children}>
27
27
  <div class="zylem-empty-state-message">{props.children}</div>
28
28
  </Show>
29
- {props.action}
29
+ <Show when={props.action}>
30
+ <div class="zylem-empty-state-actions">{props.action}</div>
31
+ </Show>
30
32
  </div>
31
33
  );
32
34
  }
@@ -0,0 +1,138 @@
1
+ import { globalStyle } from '@vanilla-extract/css';
2
+ import { vars } from '../../theme.css';
3
+ import { FRAME_SIZE } from '../AddTile/AddTile.css';
4
+ import { checkerBackground } from '../PixelGrid/PixelGrid.css';
5
+ import { artCaption } from '../ToolGroup/ToolGroup.css';
6
+
7
+ globalStyle('.zylem-frame-strip', {
8
+ display: 'flex',
9
+ flexDirection: 'column',
10
+ gap: vars.spacing.sm,
11
+ minWidth: 0,
12
+ fontFamily: vars.typography.fontFamily,
13
+ });
14
+
15
+ globalStyle('.zylem-frame-strip-header', {
16
+ display: 'flex',
17
+ alignItems: 'baseline',
18
+ gap: vars.spacing.sm,
19
+ });
20
+
21
+ globalStyle('.zylem-frame-strip-label', artCaption);
22
+
23
+ globalStyle('.zylem-frame-strip-title', {
24
+ fontSize: `calc(${vars.typography.fontSize} - 1px)`,
25
+ fontWeight: 600,
26
+ color: vars.colors.text,
27
+ });
28
+
29
+ globalStyle('.zylem-frame-strip-row', {
30
+ display: 'flex',
31
+ alignItems: 'flex-start',
32
+ gap: vars.spacing.sm,
33
+ padding: `${vars.spacing.xs} ${vars.spacing.xs} ${vars.spacing.sm}`,
34
+ overflowX: 'auto',
35
+ overflowY: 'hidden',
36
+ });
37
+
38
+ // Thumb: checker surface with the frame number in a glass badge beneath.
39
+ globalStyle('.zylem-frame-thumb', {
40
+ boxSizing: 'border-box',
41
+ position: 'relative',
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ flex: 'none',
45
+ width: FRAME_SIZE,
46
+ height: `calc(${FRAME_SIZE} + 18px)`,
47
+ padding: 0,
48
+ margin: 0,
49
+ cursor: 'pointer',
50
+ overflow: 'hidden',
51
+ borderRadius: vars.radii.control,
52
+ border: '1px solid rgba(150, 210, 255, 0.4)',
53
+ background: vars.material.fieldGlass,
54
+ boxShadow: vars.effects.shadowInset,
55
+ color: '#E8F4FF',
56
+ fontFamily: vars.typography.fontFamily,
57
+ transition: `border-color ${vars.motion.fast} ${vars.motion.easeOut}, box-shadow ${vars.motion.fast} ${vars.motion.easeOut}, transform ${vars.motion.fast} ${vars.motion.easeOut}`,
58
+ });
59
+
60
+ globalStyle('.zylem-frame-thumb:hover', {
61
+ borderColor: 'rgba(178, 219, 255, 0.8)',
62
+ transform: 'translateY(-1px)',
63
+ boxShadow: `${vars.effects.shadowInset}, ${vars.effects.glowPrimary}`,
64
+ });
65
+
66
+ globalStyle('.zylem-frame-thumb:focus-visible', {
67
+ outline: 'none',
68
+ boxShadow: `${vars.effects.shadowInset}, ${vars.effects.focusRing}`,
69
+ });
70
+
71
+ globalStyle('.zylem-frame-thumb[data-selected]', {
72
+ borderColor: 'rgba(178, 219, 255, 0.95)',
73
+ boxShadow: `${vars.effects.shadowInset}, 0 0 0 1px rgba(178, 219, 255, 0.6), ${vars.effects.glowPrimary}`,
74
+ });
75
+
76
+ globalStyle('.zylem-frame-thumb-surface', {
77
+ position: 'relative',
78
+ display: 'block',
79
+ flex: 'none',
80
+ width: '100%',
81
+ aspectRatio: '1 / 1',
82
+ vars: { '--zylem-pixel-grid-cell': '6px' },
83
+ background: checkerBackground,
84
+ imageRendering: 'pixelated',
85
+ });
86
+
87
+ globalStyle(
88
+ '.zylem-frame-thumb-surface > canvas, .zylem-frame-thumb-surface > img, .zylem-frame-thumb-surface > svg',
89
+ {
90
+ position: 'absolute',
91
+ inset: 0,
92
+ width: '100%',
93
+ height: '100%',
94
+ display: 'block',
95
+ imageRendering: 'pixelated',
96
+ }
97
+ );
98
+
99
+ globalStyle('.zylem-frame-thumb-index', {
100
+ display: 'flex',
101
+ alignItems: 'center',
102
+ justifyContent: 'center',
103
+ flex: '1 1 auto',
104
+ fontSize: '11px',
105
+ fontWeight: 600,
106
+ fontVariantNumeric: 'tabular-nums',
107
+ lineHeight: 1,
108
+ background: vars.material.panelHeaderGloss,
109
+ borderTop: '1px solid rgba(150, 210, 255, 0.25)',
110
+ });
111
+
112
+ // Actions row: commands left, step arrows right, all vertically centered.
113
+ globalStyle('.zylem-frame-actions', {
114
+ display: 'flex',
115
+ alignItems: 'center',
116
+ gap: vars.spacing.sm,
117
+ });
118
+
119
+ globalStyle('.zylem-frame-actions .zylem-button .zylem-icon', {
120
+ width: '14px',
121
+ height: '14px',
122
+ stroke: 'currentColor',
123
+ });
124
+
125
+ globalStyle('.zylem-frame-actions-spacer', {
126
+ flex: '1 1 auto',
127
+ });
128
+
129
+ globalStyle('.zylem-frame-actions-group', {
130
+ display: 'inline-flex',
131
+ alignItems: 'center',
132
+ gap: vars.spacing.xs,
133
+ });
134
+
135
+ globalStyle('.zylem-frame-actions-step', {
136
+ width: '34px',
137
+ padding: 0,
138
+ });