@zylem/ui 0.8.9 → 0.8.12
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/README.md +3 -0
- package/dist/styles.css +1 -1
- package/package.json +5 -1
- package/src/components/Accordion/Accordion.tsx +10 -1
- package/src/components/AddTile/AddTile.css.ts +17 -0
- package/src/components/AddTile/AddTile.tsx +15 -7
- package/src/components/Callout/Callout.css.ts +45 -0
- package/src/components/Callout/Callout.tsx +32 -0
- package/src/components/Card/Card.css.ts +18 -0
- package/src/components/Card/Card.tsx +25 -2
- package/src/components/Dialog/Dialog.css.ts +69 -2
- package/src/components/Dialog/Dialog.tsx +35 -7
- package/src/components/DropdownMenu/DropdownMenu.css.ts +22 -0
- package/src/components/DropdownMenu/DropdownMenu.tsx +67 -20
- package/src/components/EmptyState/EmptyState.css.ts +9 -0
- package/src/components/EmptyState/EmptyState.tsx +3 -1
- package/src/components/FrameStrip/FrameStrip.css.ts +6 -0
- package/src/components/FrameStrip/FrameStrip.tsx +30 -2
- package/src/components/Menubar/Menubar.css.ts +52 -0
- package/src/components/Menubar/Menubar.tsx +77 -0
- package/src/components/Mixer/Knob.tsx +188 -0
- package/src/components/Mixer/Mixer.css.ts +246 -0
- package/src/components/Mixer/MixerStrip.tsx +150 -0
- package/src/components/NumberField/NumberField.tsx +5 -1
- package/src/components/Panel/Panel.tsx +6 -5
- package/src/components/PianoRoll/PianoRoll.css.ts +224 -0
- package/src/components/PianoRoll/PianoRoll.tsx +353 -0
- package/src/components/PianoRoll/piano-roll-math.ts +89 -0
- package/src/components/Property/Property.css.ts +1 -1
- package/src/components/Property/Property.tsx +3 -0
- package/src/components/Sidebar/Sidebar.css.ts +8 -0
- package/src/components/Sidebar/Sidebar.tsx +10 -2
- package/src/components/Slider/Slider.tsx +6 -0
- package/src/components/SpriteSizePicker/SpriteSizePicker.tsx +15 -4
- package/src/components/Tabs/Tabs.css.ts +48 -0
- package/src/components/Tabs/Tabs.tsx +41 -3
- package/src/components/TextField/TextField.tsx +49 -5
- package/src/components/TimelineLanes/TimelineLanes.css.ts +365 -0
- package/src/components/TimelineLanes/TimelineLanes.tsx +509 -0
- package/src/components/TimelineLanes/lanes-math.ts +182 -0
- package/src/components/Toast/Toast.css.ts +48 -0
- package/src/components/Toast/Toast.tsx +37 -0
- package/src/components/TrackHeader/TrackHeader.css.ts +162 -0
- package/src/components/TrackHeader/TrackHeader.tsx +145 -0
- package/src/components/TransportBar/TransportBar.css.ts +65 -0
- package/src/components/TransportBar/TransportBar.tsx +154 -0
- package/src/components/Waveform/Waveform.css.ts +77 -0
- package/src/components/Waveform/Waveform.tsx +195 -0
- package/src/components/Waveform/peaks.ts +49 -0
- package/src/components/index.ts +72 -1
- package/src/global/accordion.css.ts +23 -0
- package/src/icons/create-icon.tsx +71 -0
- package/src/icons/icon-nodes.ts +75 -0
- package/src/icons/index.ts +54 -0
- package/src/icons/svg/camera-isometric.svg +16 -0
- package/src/icons/svg/free-perspective.svg +8 -0
- package/src/icons/svg/game-camera.svg +6 -0
- package/src/icons/svg/perspective.svg +5 -0
- package/src/icons/svg/sideview-perspective.svg +5 -0
- package/src/icons/svg/top-down-perspective.svg +5 -0
- package/src/styles.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zylem/ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.12",
|
|
4
4
|
"description": "Design tokens, global styles, and Solid components for Zylem, built with vanilla-extract",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"types": "./vite/index.ts",
|
|
43
43
|
"import": "./vite/index.ts"
|
|
44
44
|
},
|
|
45
|
+
"./icons": {
|
|
46
|
+
"types": "./src/icons/index.ts",
|
|
47
|
+
"import": "./src/icons/index.ts"
|
|
48
|
+
},
|
|
45
49
|
"./styles.css": "./dist/styles.css"
|
|
46
50
|
},
|
|
47
51
|
"files": [
|
|
@@ -18,6 +18,13 @@ export interface AccordionProps {
|
|
|
18
18
|
class?: string;
|
|
19
19
|
/** Extra class applied to each content region (e.g. scroll helpers). */
|
|
20
20
|
contentClass?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Inspector layout: sections take their natural height and the accordion
|
|
23
|
+
* grows with them, so the surrounding panel scrolls as a whole. Without it
|
|
24
|
+
* the accordion fills its container and expanded sections share the space
|
|
25
|
+
* and scroll individually (the editor sidebar behaviour).
|
|
26
|
+
*/
|
|
27
|
+
stack?: boolean;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
/**
|
|
@@ -28,7 +35,9 @@ export interface AccordionProps {
|
|
|
28
35
|
export function Accordion(props: AccordionProps) {
|
|
29
36
|
return (
|
|
30
37
|
<KAccordion.Root
|
|
31
|
-
class={props.
|
|
38
|
+
class={['zylem-accordion', props.stack ? 'zylem-accordion--stack' : '', props.class ?? '']
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.join(' ')}
|
|
32
41
|
multiple={props.multiple}
|
|
33
42
|
collapsible={props.collapsible ?? true}
|
|
34
43
|
value={props.value}
|
|
@@ -36,6 +36,14 @@ globalStyle('.zylem-add-tile[data-variant="tall"]', {
|
|
|
36
36
|
height: `calc(${FRAME_SIZE} + 18px)`,
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
// Stands in for a card: fills its grid cell and rounds like one.
|
|
40
|
+
globalStyle('.zylem-add-tile[data-variant="fill"]', {
|
|
41
|
+
width: '100%',
|
|
42
|
+
height: '100%',
|
|
43
|
+
minHeight: '110px',
|
|
44
|
+
borderRadius: vars.radii.card,
|
|
45
|
+
});
|
|
46
|
+
|
|
39
47
|
globalStyle('.zylem-add-tile:hover:not([disabled])', {
|
|
40
48
|
color: '#E8F4FF',
|
|
41
49
|
borderColor: 'rgba(178, 219, 255, 0.9)',
|
|
@@ -70,9 +78,18 @@ globalStyle('.zylem-add-tile[data-variant="tall"] .zylem-add-tile-icon', {
|
|
|
70
78
|
height: '20px',
|
|
71
79
|
});
|
|
72
80
|
|
|
81
|
+
globalStyle('.zylem-add-tile[data-variant="fill"] .zylem-add-tile-icon', {
|
|
82
|
+
width: '24px',
|
|
83
|
+
height: '24px',
|
|
84
|
+
});
|
|
85
|
+
|
|
73
86
|
globalStyle('.zylem-add-tile-label', {
|
|
74
87
|
fontSize: '11px',
|
|
75
88
|
fontWeight: 600,
|
|
76
89
|
lineHeight: 1,
|
|
77
90
|
whiteSpace: 'nowrap',
|
|
78
91
|
});
|
|
92
|
+
|
|
93
|
+
globalStyle('.zylem-add-tile[data-variant="fill"] .zylem-add-tile-label', {
|
|
94
|
+
fontSize: '13px',
|
|
95
|
+
});
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
import Plus from 'lucide-solid/icons/plus';
|
|
2
2
|
import { Show } from 'solid-js';
|
|
3
3
|
|
|
4
|
+
export type AddTileVariant = 'square' | 'tall' | 'fill';
|
|
5
|
+
|
|
4
6
|
export interface AddTileProps {
|
|
5
|
-
/** Text under the plus (tall
|
|
7
|
+
/** Text under the plus (tall and fill variants) and the accessible name. */
|
|
6
8
|
label?: string;
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* `square` matches a swatch; `tall` matches a frame thumb with room for
|
|
11
|
+
* text; `fill` takes the whole grid cell it is placed in, so it can stand
|
|
12
|
+
* in for a card ("New project") in a card grid.
|
|
13
|
+
*/
|
|
14
|
+
variant?: AddTileVariant;
|
|
9
15
|
onClick?: (event: MouseEvent) => void;
|
|
10
16
|
disabled?: boolean;
|
|
11
17
|
class?: string;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
|
-
* Dashed "add" tile that sits at the end of a swatch row
|
|
16
|
-
* plus is centered on its own; the tall
|
|
21
|
+
* Dashed "add" tile that sits at the end of a swatch row, frame strip, or
|
|
22
|
+
* card grid. The plus is centered on its own; the tall and fill variants
|
|
23
|
+
* stack a label beneath it.
|
|
17
24
|
*/
|
|
18
25
|
export function AddTile(props: AddTileProps) {
|
|
19
26
|
const variant = () => props.variant ?? 'square';
|
|
20
27
|
const label = () => props.label ?? 'Add';
|
|
28
|
+
const showsLabel = () => variant() !== 'square' && Boolean(props.label);
|
|
21
29
|
return (
|
|
22
30
|
<button
|
|
23
31
|
type="button"
|
|
24
32
|
class={props.class ? `zylem-add-tile ${props.class}` : 'zylem-add-tile'}
|
|
25
33
|
data-variant={variant()}
|
|
26
|
-
aria-label={
|
|
34
|
+
aria-label={showsLabel() ? undefined : label()}
|
|
27
35
|
title={variant() === 'square' ? label() : undefined}
|
|
28
36
|
disabled={props.disabled}
|
|
29
37
|
onClick={props.onClick}
|
|
30
38
|
>
|
|
31
39
|
<Plus class="zylem-icon zylem-add-tile-icon" aria-hidden="true" />
|
|
32
|
-
<Show when={
|
|
40
|
+
<Show when={showsLabel()}>
|
|
33
41
|
<span class="zylem-add-tile-label">{props.label}</span>
|
|
34
42
|
</Show>
|
|
35
43
|
</button>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
|
|
4
|
+
globalStyle('.zylem-callout', {
|
|
5
|
+
boxSizing: 'border-box',
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexDirection: 'column',
|
|
8
|
+
gap: vars.spacing.xxs,
|
|
9
|
+
padding: `${vars.spacing.sm} ${vars.spacing.md}`,
|
|
10
|
+
borderLeft: '2px solid rgba(150, 210, 255, 0.7)',
|
|
11
|
+
borderRadius: `0 ${vars.radii.control} ${vars.radii.control} 0`,
|
|
12
|
+
background: 'rgba(97, 166, 232, 0.1)',
|
|
13
|
+
color: vars.colors.text,
|
|
14
|
+
fontFamily: vars.typography.fontFamily,
|
|
15
|
+
fontSize: `calc(${vars.typography.fontSize} - 1px)`,
|
|
16
|
+
lineHeight: 1.45,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
globalStyle('.zylem-callout-title', {
|
|
20
|
+
fontWeight: 700,
|
|
21
|
+
letterSpacing: '0.02em',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
globalStyle('.zylem-callout-message code', {
|
|
25
|
+
fontFamily: "'Courier New', monospace",
|
|
26
|
+
fontSize: '0.95em',
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
globalStyle('.zylem-callout[data-tone="warning"]', {
|
|
30
|
+
borderLeftColor: 'rgba(252, 211, 77, 0.8)',
|
|
31
|
+
background: 'rgba(252, 211, 77, 0.09)',
|
|
32
|
+
color: '#FDE68A',
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
globalStyle('.zylem-callout[data-tone="error"]', {
|
|
36
|
+
borderLeftColor: 'rgba(255, 132, 114, 0.85)',
|
|
37
|
+
background: 'rgba(230, 69, 52, 0.14)',
|
|
38
|
+
color: '#FFD9D3',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
globalStyle('.zylem-callout[data-tone="success"]', {
|
|
42
|
+
borderLeftColor: 'rgba(120, 255, 150, 0.7)',
|
|
43
|
+
background: 'rgba(60, 200, 100, 0.12)',
|
|
44
|
+
color: '#D9FFE3',
|
|
45
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Show } from 'solid-js';
|
|
2
|
+
import type { JSX } from 'solid-js';
|
|
3
|
+
|
|
4
|
+
export type CalloutTone = 'info' | 'warning' | 'error' | 'success';
|
|
5
|
+
|
|
6
|
+
export interface CalloutProps {
|
|
7
|
+
tone?: CalloutTone;
|
|
8
|
+
/** Short bold lead-in before the message. */
|
|
9
|
+
title?: JSX.Element;
|
|
10
|
+
children: JSX.Element;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Inline note: a tinted strip with a coloured left rule for hints, warnings,
|
|
16
|
+
* and errors that belong next to the thing they describe (a form, a footer).
|
|
17
|
+
* For transient messages use `Toast`; for one-word labels use `Badge`.
|
|
18
|
+
*/
|
|
19
|
+
export function Callout(props: CalloutProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
class={props.class ? `zylem-callout ${props.class}` : 'zylem-callout'}
|
|
23
|
+
role={props.tone === 'error' ? 'alert' : 'note'}
|
|
24
|
+
data-tone={props.tone ?? 'info'}
|
|
25
|
+
>
|
|
26
|
+
<Show when={props.title}>
|
|
27
|
+
<span class="zylem-callout-title">{props.title}</span>
|
|
28
|
+
</Show>
|
|
29
|
+
<span class="zylem-callout-message">{props.children}</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -45,6 +45,24 @@ globalStyle('.zylem-card--selected', {
|
|
|
45
45
|
boxShadow: `0 8px 20px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.08), ${vars.effects.glowActive}`,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
// Artwork well: fills the card width, clips its content to the control radius.
|
|
49
|
+
globalStyle('.zylem-card-media', {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
justifyContent: 'center',
|
|
53
|
+
minHeight: '64px',
|
|
54
|
+
borderRadius: vars.radii.control,
|
|
55
|
+
overflow: 'hidden',
|
|
56
|
+
color: 'rgba(255, 255, 255, 0.85)',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
globalStyle('.zylem-card-media > img', {
|
|
60
|
+
display: 'block',
|
|
61
|
+
width: '100%',
|
|
62
|
+
height: '100%',
|
|
63
|
+
objectFit: 'cover',
|
|
64
|
+
});
|
|
65
|
+
|
|
48
66
|
globalStyle('.zylem-card-title', {
|
|
49
67
|
fontSize: vars.typography.fontSize,
|
|
50
68
|
fontWeight: 700,
|
|
@@ -5,24 +5,30 @@ export interface CardProps
|
|
|
5
5
|
extends Omit<JSX.HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
6
6
|
title?: JSX.Element;
|
|
7
7
|
description?: JSX.Element;
|
|
8
|
+
/** Artwork above the title: a thumbnail, preview swatch, or icon well. */
|
|
9
|
+
media?: JSX.Element;
|
|
8
10
|
/** Renders hover glow + pointer cursor and click affordances. */
|
|
9
11
|
interactive?: boolean;
|
|
10
12
|
selected?: boolean;
|
|
13
|
+
/** Rendered after the description: tags, actions, metadata. */
|
|
11
14
|
children?: JSX.Element;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* HyperGlass card: small index-card tile with a blue glow. Use for entity
|
|
16
|
-
* tiles, project tiles, and asset thumbnails.
|
|
19
|
+
* tiles, project tiles, and asset thumbnails. Order is media, title,
|
|
20
|
+
* description, then children.
|
|
17
21
|
*/
|
|
18
22
|
export function Card(props: CardProps) {
|
|
19
23
|
const [local, rest] = splitProps(props, [
|
|
20
24
|
'title',
|
|
21
25
|
'description',
|
|
26
|
+
'media',
|
|
22
27
|
'interactive',
|
|
23
28
|
'selected',
|
|
24
29
|
'children',
|
|
25
30
|
'class',
|
|
31
|
+
'onKeyDown',
|
|
26
32
|
]);
|
|
27
33
|
|
|
28
34
|
const classes = () =>
|
|
@@ -35,20 +41,37 @@ export function Card(props: CardProps) {
|
|
|
35
41
|
.filter(Boolean)
|
|
36
42
|
.join(' ');
|
|
37
43
|
|
|
44
|
+
// An interactive card is a button in all but tag name, so Enter and Space
|
|
45
|
+
// activate it like one.
|
|
46
|
+
const onKeyDown = (event: KeyboardEvent & { currentTarget: HTMLDivElement; target: Element }) => {
|
|
47
|
+
if (typeof local.onKeyDown === 'function') local.onKeyDown(event);
|
|
48
|
+
else if (local.onKeyDown) local.onKeyDown[0](local.onKeyDown[1], event);
|
|
49
|
+
if (!local.interactive || event.defaultPrevented) return;
|
|
50
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
event.currentTarget.click();
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
38
56
|
return (
|
|
39
57
|
<div
|
|
40
58
|
class={classes()}
|
|
41
59
|
role={local.interactive ? 'button' : undefined}
|
|
42
60
|
tabIndex={local.interactive ? 0 : undefined}
|
|
61
|
+
aria-pressed={local.interactive && local.selected !== undefined ? local.selected : undefined}
|
|
62
|
+
onKeyDown={onKeyDown}
|
|
43
63
|
{...rest}
|
|
44
64
|
>
|
|
65
|
+
<Show when={local.media}>
|
|
66
|
+
<div class="zylem-card-media">{local.media}</div>
|
|
67
|
+
</Show>
|
|
45
68
|
<Show when={local.title}>
|
|
46
69
|
<div class="zylem-card-title">{local.title}</div>
|
|
47
70
|
</Show>
|
|
48
|
-
{local.children}
|
|
49
71
|
<Show when={local.description}>
|
|
50
72
|
<div class="zylem-card-description">{local.description}</div>
|
|
51
73
|
</Show>
|
|
74
|
+
{local.children}
|
|
52
75
|
</div>
|
|
53
76
|
);
|
|
54
77
|
}
|
|
@@ -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.
|
|
57
|
-
padding:
|
|
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=
|
|
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
|
-
<
|
|
52
|
-
{props.title}
|
|
53
|
-
|
|
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
|
|
58
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
30
|
-
*
|
|
38
|
+
* A menu with any checkable row reserves the check column on every row so
|
|
39
|
+
* labels line up.
|
|
31
40
|
*/
|
|
32
|
-
export function
|
|
33
|
-
const
|
|
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
|
|
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
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
}
|