@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.
- package/dist/chunk-V47CGFWX.js +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.js +1 -1
- package/package.json +22 -11
- package/src/components/Accordion/Accordion.tsx +10 -1
- package/src/components/AddTile/AddTile.css.ts +95 -0
- package/src/components/AddTile/AddTile.tsx +45 -0
- package/src/components/BuildLabel/BuildLabel.css.ts +31 -0
- package/src/components/BuildLabel/BuildLabel.tsx +37 -0
- package/src/components/BuildLabel/mount-build-label.ts +77 -0
- package/src/components/BuildLabel/resolve-build-version.ts +47 -0
- 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/ColorPalette/ColorPalette.css.ts +51 -0
- package/src/components/ColorPalette/ColorPalette.tsx +78 -0
- 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 +138 -0
- package/src/components/FrameStrip/FrameStrip.tsx +146 -0
- 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/PixelGrid/PixelGrid.css.ts +30 -0
- package/src/components/PixelGrid/PixelGrid.tsx +48 -0
- package/src/components/PlaybackControls/PlaybackControls.css.ts +53 -0
- package/src/components/PlaybackControls/PlaybackControls.tsx +80 -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.css.ts +103 -0
- package/src/components/SpriteSizePicker/SpriteSizePicker.tsx +172 -0
- package/src/components/SpriteSizePicker/snap-size.ts +28 -0
- 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/TimelineTrack/TimelineTrack.css.ts +163 -0
- package/src/components/TimelineTrack/TimelineTrack.tsx +90 -0
- package/src/components/TimelineTrack/timeline-math.ts +43 -0
- package/src/components/Toast/Toast.css.ts +48 -0
- package/src/components/Toast/Toast.tsx +37 -0
- package/src/components/ToolButton/ToolButton.tsx +92 -0
- package/src/components/ToolGroup/ToolGroup.css.ts +35 -0
- package/src/components/ToolGroup/ToolGroup.tsx +29 -0
- package/src/components/ToolboxButton/ToolboxButton.css.ts +131 -0
- package/src/components/ToolboxButton/ToolboxButton.tsx +88 -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 +122 -0
- package/src/global/accordion.css.ts +23 -0
- package/src/global/hyperglass-base.css.ts +9 -6
- package/src/styles.ts +23 -0
- package/src/theme.css.ts +15 -0
- package/vite/collect-zylem-versions.ts +82 -0
- package/vite/index.ts +30 -0
- package/dist/chunk-3TP2SNNW.js +0 -1
package/dist/styles.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./chunk-
|
|
1
|
+
import"./chunk-V47CGFWX.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zylem/ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.11",
|
|
4
4
|
"description": "Design tokens, global styles, and Solid components for Zylem, built with vanilla-extract",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,12 +34,30 @@
|
|
|
34
34
|
"types": "./src/components/index.ts",
|
|
35
35
|
"import": "./src/components/index.ts"
|
|
36
36
|
},
|
|
37
|
+
"./build-label": {
|
|
38
|
+
"types": "./src/components/BuildLabel/mount-build-label.ts",
|
|
39
|
+
"import": "./src/components/BuildLabel/mount-build-label.ts"
|
|
40
|
+
},
|
|
41
|
+
"./vite": {
|
|
42
|
+
"types": "./vite/index.ts",
|
|
43
|
+
"import": "./vite/index.ts"
|
|
44
|
+
},
|
|
37
45
|
"./styles.css": "./dist/styles.css"
|
|
38
46
|
},
|
|
39
47
|
"files": [
|
|
40
48
|
"dist",
|
|
41
|
-
"src"
|
|
49
|
+
"src",
|
|
50
|
+
"vite"
|
|
42
51
|
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"prepublishOnly": "NODE_ENV=production tsup",
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"showcase": "vite --host",
|
|
57
|
+
"test": "vitest run",
|
|
58
|
+
"test:watch": "vitest",
|
|
59
|
+
"lint": "biome check ."
|
|
60
|
+
},
|
|
43
61
|
"peerDependencies": {
|
|
44
62
|
"solid-js": "^1.9.9"
|
|
45
63
|
},
|
|
@@ -73,12 +91,5 @@
|
|
|
73
91
|
"node": ">=22.12.0",
|
|
74
92
|
"pnpm": ">=10.32.1"
|
|
75
93
|
},
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
"dev": "tsup --watch",
|
|
79
|
-
"showcase": "vite --host",
|
|
80
|
-
"test": "vitest run",
|
|
81
|
-
"test:watch": "vitest",
|
|
82
|
-
"lint": "biome check ."
|
|
83
|
-
}
|
|
84
|
-
}
|
|
94
|
+
"packageManager": "pnpm@10.33.0"
|
|
95
|
+
}
|
|
@@ -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}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
|
|
4
|
+
/** Swatch / tile edge length shared by the palette and add tiles. */
|
|
5
|
+
export const TILE_SIZE = '26px';
|
|
6
|
+
/** Frame thumb edge length shared by the strip and the tall add tile. */
|
|
7
|
+
export const FRAME_SIZE = '56px';
|
|
8
|
+
|
|
9
|
+
globalStyle('.zylem-add-tile', {
|
|
10
|
+
boxSizing: 'border-box',
|
|
11
|
+
display: 'inline-flex',
|
|
12
|
+
flexDirection: 'column',
|
|
13
|
+
alignItems: 'center',
|
|
14
|
+
justifyContent: 'center',
|
|
15
|
+
gap: vars.spacing.xs,
|
|
16
|
+
flex: 'none',
|
|
17
|
+
padding: 0,
|
|
18
|
+
margin: 0,
|
|
19
|
+
cursor: 'pointer',
|
|
20
|
+
userSelect: 'none',
|
|
21
|
+
color: vars.colors.textSecondary,
|
|
22
|
+
fontFamily: vars.typography.fontFamily,
|
|
23
|
+
borderRadius: vars.radii.control,
|
|
24
|
+
border: '1px dashed rgba(150, 210, 255, 0.55)',
|
|
25
|
+
background: 'rgba(97, 166, 232, 0.06)',
|
|
26
|
+
transition: `color ${vars.motion.fast} ${vars.motion.easeOut}, border-color ${vars.motion.fast} ${vars.motion.easeOut}, background ${vars.motion.fast} ${vars.motion.easeOut}, box-shadow ${vars.motion.fast} ${vars.motion.easeOut}`,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
globalStyle('.zylem-add-tile[data-variant="square"]', {
|
|
30
|
+
width: TILE_SIZE,
|
|
31
|
+
height: TILE_SIZE,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
globalStyle('.zylem-add-tile[data-variant="tall"]', {
|
|
35
|
+
width: FRAME_SIZE,
|
|
36
|
+
height: `calc(${FRAME_SIZE} + 18px)`,
|
|
37
|
+
});
|
|
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
|
+
|
|
47
|
+
globalStyle('.zylem-add-tile:hover:not([disabled])', {
|
|
48
|
+
color: '#E8F4FF',
|
|
49
|
+
borderColor: 'rgba(178, 219, 255, 0.9)',
|
|
50
|
+
background: 'rgba(97, 166, 232, 0.16)',
|
|
51
|
+
boxShadow: vars.effects.glowPrimary,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
globalStyle('.zylem-add-tile:active:not([disabled])', {
|
|
55
|
+
background: 'rgba(97, 166, 232, 0.24)',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
globalStyle('.zylem-add-tile:focus-visible', {
|
|
59
|
+
outline: 'none',
|
|
60
|
+
boxShadow: vars.effects.focusRing,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
globalStyle('.zylem-add-tile[disabled]', {
|
|
64
|
+
opacity: 0.45,
|
|
65
|
+
cursor: 'not-allowed',
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
globalStyle('.zylem-add-tile-icon', {
|
|
69
|
+
display: 'block',
|
|
70
|
+
width: '16px',
|
|
71
|
+
height: '16px',
|
|
72
|
+
stroke: 'currentColor',
|
|
73
|
+
flex: 'none',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
globalStyle('.zylem-add-tile[data-variant="tall"] .zylem-add-tile-icon', {
|
|
77
|
+
width: '20px',
|
|
78
|
+
height: '20px',
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
globalStyle('.zylem-add-tile[data-variant="fill"] .zylem-add-tile-icon', {
|
|
82
|
+
width: '24px',
|
|
83
|
+
height: '24px',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
globalStyle('.zylem-add-tile-label', {
|
|
87
|
+
fontSize: '11px',
|
|
88
|
+
fontWeight: 600,
|
|
89
|
+
lineHeight: 1,
|
|
90
|
+
whiteSpace: 'nowrap',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
globalStyle('.zylem-add-tile[data-variant="fill"] .zylem-add-tile-label', {
|
|
94
|
+
fontSize: '13px',
|
|
95
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Plus from 'lucide-solid/icons/plus';
|
|
2
|
+
import { Show } from 'solid-js';
|
|
3
|
+
|
|
4
|
+
export type AddTileVariant = 'square' | 'tall' | 'fill';
|
|
5
|
+
|
|
6
|
+
export interface AddTileProps {
|
|
7
|
+
/** Text under the plus (tall and fill variants) and the accessible name. */
|
|
8
|
+
label?: string;
|
|
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;
|
|
15
|
+
onClick?: (event: MouseEvent) => void;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
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.
|
|
24
|
+
*/
|
|
25
|
+
export function AddTile(props: AddTileProps) {
|
|
26
|
+
const variant = () => props.variant ?? 'square';
|
|
27
|
+
const label = () => props.label ?? 'Add';
|
|
28
|
+
const showsLabel = () => variant() !== 'square' && Boolean(props.label);
|
|
29
|
+
return (
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
class={props.class ? `zylem-add-tile ${props.class}` : 'zylem-add-tile'}
|
|
33
|
+
data-variant={variant()}
|
|
34
|
+
aria-label={showsLabel() ? undefined : label()}
|
|
35
|
+
title={variant() === 'square' ? label() : undefined}
|
|
36
|
+
disabled={props.disabled}
|
|
37
|
+
onClick={props.onClick}
|
|
38
|
+
>
|
|
39
|
+
<Plus class="zylem-icon zylem-add-tile-icon" aria-hidden="true" />
|
|
40
|
+
<Show when={showsLabel()}>
|
|
41
|
+
<span class="zylem-add-tile-label">{props.label}</span>
|
|
42
|
+
</Show>
|
|
43
|
+
</button>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Tiny corner HUD for the resolved Zylem build number. Sits above game
|
|
6
|
+
* chrome (`tooltip` tier) and stays out of the way of the editor toggle
|
|
7
|
+
* (bottom-right).
|
|
8
|
+
*/
|
|
9
|
+
globalStyle('.zylem-build-label', {
|
|
10
|
+
position: 'fixed',
|
|
11
|
+
top: '10px',
|
|
12
|
+
right: '12px',
|
|
13
|
+
zIndex: vars.layers.tooltip,
|
|
14
|
+
pointerEvents: 'auto',
|
|
15
|
+
padding: `2px ${vars.spacing.sm}`,
|
|
16
|
+
borderRadius: '5px',
|
|
17
|
+
border: '1px solid rgba(97, 166, 232, 0.35)',
|
|
18
|
+
background:
|
|
19
|
+
'linear-gradient(180deg, rgba(60, 80, 105, 0.55), rgba(26, 31, 39, 0.78))',
|
|
20
|
+
boxShadow:
|
|
21
|
+
'inset 0 1px 0 rgba(255,255,255,0.22), 0 2px 8px rgba(0,0,0,0.35)',
|
|
22
|
+
color: vars.colors.textSecondary,
|
|
23
|
+
fontFamily: vars.typography.fontFamily,
|
|
24
|
+
fontSize: '10px',
|
|
25
|
+
fontWeight: 700,
|
|
26
|
+
letterSpacing: '0.06em',
|
|
27
|
+
lineHeight: 1.4,
|
|
28
|
+
textShadow: '0 1px 1px rgba(0,0,0,0.55)',
|
|
29
|
+
userSelect: 'none',
|
|
30
|
+
whiteSpace: 'nowrap',
|
|
31
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createMemo, onMount } from 'solid-js';
|
|
2
|
+
import {
|
|
3
|
+
formatBuildLabel,
|
|
4
|
+
packagesTitle,
|
|
5
|
+
resolveBuildVersion,
|
|
6
|
+
type BuildLabelModel,
|
|
7
|
+
} from './resolve-build-version';
|
|
8
|
+
import { BUILD_LABEL_ID, ensureFallbackStyles } from './mount-build-label';
|
|
9
|
+
|
|
10
|
+
export type BuildLabelProps = BuildLabelModel;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Fixed top-right HUD showing the resolved Zylem build number.
|
|
14
|
+
*
|
|
15
|
+
* Demo and showcase apps mount this (or `mountBuildLabel`) so the engine
|
|
16
|
+
* version is visible without opening DevTools. Creator is the exception:
|
|
17
|
+
* it shows only its own version in the menu bar.
|
|
18
|
+
*/
|
|
19
|
+
export function BuildLabel(props: BuildLabelProps) {
|
|
20
|
+
const label = createMemo(() => formatBuildLabel(resolveBuildVersion(props)));
|
|
21
|
+
const title = createMemo(() => packagesTitle(props.packages));
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
ensureFallbackStyles();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
id={BUILD_LABEL_ID}
|
|
30
|
+
class="zylem-build-label"
|
|
31
|
+
aria-label="Build version"
|
|
32
|
+
title={title()}
|
|
33
|
+
>
|
|
34
|
+
{label()}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBuildLabel,
|
|
3
|
+
packagesTitle,
|
|
4
|
+
resolveBuildVersion,
|
|
5
|
+
type BuildLabelModel,
|
|
6
|
+
} from './resolve-build-version';
|
|
7
|
+
|
|
8
|
+
export type { BuildLabelModel };
|
|
9
|
+
|
|
10
|
+
export const BUILD_LABEL_ID = 'zylem-build-label';
|
|
11
|
+
export const BUILD_LABEL_STYLE_ID = 'zylem-build-label-styles';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fallback rules so the HUD still paints in apps that do not import
|
|
15
|
+
* `@zylem/ui/styles.css` (arena). Themed apps get the vanilla-extract class
|
|
16
|
+
* as well; both targets use the same selectors so they do not fight.
|
|
17
|
+
*/
|
|
18
|
+
const FALLBACK_CSS = `
|
|
19
|
+
#${BUILD_LABEL_ID} {
|
|
20
|
+
position: fixed;
|
|
21
|
+
top: 10px;
|
|
22
|
+
right: 12px;
|
|
23
|
+
z-index: var(--zylem-layer-tooltip, 4000);
|
|
24
|
+
pointer-events: auto;
|
|
25
|
+
padding: 2px 8px;
|
|
26
|
+
border-radius: 5px;
|
|
27
|
+
border: 1px solid rgba(97, 166, 232, 0.35);
|
|
28
|
+
background: linear-gradient(180deg, rgba(60, 80, 105, 0.55), rgba(26, 31, 39, 0.78));
|
|
29
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 2px 8px rgba(0, 0, 0, 0.35);
|
|
30
|
+
color: #c5d0da;
|
|
31
|
+
font-family: var(--zylem-font-family, 'Exo 2', sans-serif);
|
|
32
|
+
font-size: 10px;
|
|
33
|
+
font-weight: 700;
|
|
34
|
+
letter-spacing: 0.06em;
|
|
35
|
+
line-height: 1.4;
|
|
36
|
+
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.55);
|
|
37
|
+
user-select: none;
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
export function ensureFallbackStyles(): void {
|
|
43
|
+
if (typeof document === 'undefined') return;
|
|
44
|
+
if (document.getElementById(BUILD_LABEL_STYLE_ID)) return;
|
|
45
|
+
const style = document.createElement('style');
|
|
46
|
+
style.id = BUILD_LABEL_STYLE_ID;
|
|
47
|
+
style.textContent = FALLBACK_CSS;
|
|
48
|
+
document.head.append(style);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Mount the build-number HUD on `parent` (defaults to `document.body`).
|
|
53
|
+
* Replaces a previous mount with the same id.
|
|
54
|
+
*/
|
|
55
|
+
export function mountBuildLabel(
|
|
56
|
+
model: BuildLabelModel,
|
|
57
|
+
parent: ParentNode = document.body
|
|
58
|
+
): HTMLElement {
|
|
59
|
+
ensureFallbackStyles();
|
|
60
|
+
parent.querySelector(`#${BUILD_LABEL_ID}`)?.remove();
|
|
61
|
+
|
|
62
|
+
const el = document.createElement('div');
|
|
63
|
+
el.id = BUILD_LABEL_ID;
|
|
64
|
+
el.className = 'zylem-build-label';
|
|
65
|
+
el.setAttribute('aria-label', 'Build version');
|
|
66
|
+
el.textContent = formatBuildLabel(resolveBuildVersion(model));
|
|
67
|
+
const title = packagesTitle(model.packages);
|
|
68
|
+
if (title) el.title = title;
|
|
69
|
+
|
|
70
|
+
if (model.packages) {
|
|
71
|
+
(globalThis as { __ZYLEM_PACKAGE_VERSIONS__?: Record<string, string> }).__ZYLEM_PACKAGE_VERSIONS__ =
|
|
72
|
+
model.packages;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
parent.append(el);
|
|
76
|
+
return el;
|
|
77
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface BuildLabelModel {
|
|
2
|
+
/** Host app version, used when `packages` does not resolve a preferred entry. */
|
|
3
|
+
version?: string;
|
|
4
|
+
/** Resolved `@zylem/*` package versions collected at build time. */
|
|
5
|
+
packages?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const PREFERRED_PACKAGES = [
|
|
9
|
+
'@zylem/game-lib',
|
|
10
|
+
'@zylem/editor',
|
|
11
|
+
'@zylem/ui',
|
|
12
|
+
'@zylem/behaviors',
|
|
13
|
+
'@zylem/runtime',
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pick the version to show as the HUD build number.
|
|
18
|
+
*
|
|
19
|
+
* Demo/showcase apps prefer the engine (`@zylem/game-lib`) so the overlay
|
|
20
|
+
* reports the stack being exercised rather than the host app's own version.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveBuildVersion(model: BuildLabelModel): string {
|
|
23
|
+
const packages = model.packages;
|
|
24
|
+
if (packages) {
|
|
25
|
+
for (const name of PREFERRED_PACKAGES) {
|
|
26
|
+
const version = packages[name];
|
|
27
|
+
if (version) return version;
|
|
28
|
+
}
|
|
29
|
+
const first = Object.values(packages)[0];
|
|
30
|
+
if (first) return first;
|
|
31
|
+
}
|
|
32
|
+
return model.version ?? '';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Display form used in the HUD: the version prefixed with `v`. */
|
|
36
|
+
export function formatBuildLabel(version: string): string {
|
|
37
|
+
if (!version) return '';
|
|
38
|
+
return version.startsWith('v') ? version : `v${version}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Hover text listing every collected package. */
|
|
42
|
+
export function packagesTitle(packages: Record<string, string> | undefined): string | undefined {
|
|
43
|
+
if (!packages || Object.keys(packages).length === 0) return undefined;
|
|
44
|
+
return Object.entries(packages)
|
|
45
|
+
.map(([name, version]) => `${name} ${version}`)
|
|
46
|
+
.join('\n');
|
|
47
|
+
}
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { vars } from '../../theme.css';
|
|
3
|
+
import { TILE_SIZE } from '../AddTile/AddTile.css';
|
|
4
|
+
import { checkerBackground } from '../PixelGrid/PixelGrid.css';
|
|
5
|
+
|
|
6
|
+
globalStyle('.zylem-palette', {
|
|
7
|
+
display: 'flex',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
flexWrap: 'wrap',
|
|
10
|
+
gap: vars.spacing.sm,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
globalStyle('.zylem-swatch', {
|
|
14
|
+
boxSizing: 'border-box',
|
|
15
|
+
width: TILE_SIZE,
|
|
16
|
+
height: TILE_SIZE,
|
|
17
|
+
flex: 'none',
|
|
18
|
+
padding: 0,
|
|
19
|
+
margin: 0,
|
|
20
|
+
cursor: 'pointer',
|
|
21
|
+
borderRadius: vars.radii.control,
|
|
22
|
+
border: '1px solid rgba(150, 210, 255, 0.4)',
|
|
23
|
+
background: 'var(--zylem-swatch-color, transparent)',
|
|
24
|
+
boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.22), 0 1px 3px rgba(0,0,0,0.4)',
|
|
25
|
+
transition: `transform ${vars.motion.fast} ${vars.motion.easeOut}, box-shadow ${vars.motion.fast} ${vars.motion.easeOut}`,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
globalStyle('.zylem-swatch[data-transparent]', {
|
|
29
|
+
vars: { '--zylem-pixel-grid-cell': '5px' },
|
|
30
|
+
background: checkerBackground,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
globalStyle('.zylem-swatch:hover:not([disabled])', {
|
|
34
|
+
transform: 'translateY(-1px)',
|
|
35
|
+
boxShadow: `inset 0 1px 0 rgba(255,255,255,0.3), 0 2px 6px rgba(0,0,0,0.45), ${vars.effects.glowPrimary}`,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
globalStyle('.zylem-swatch:focus-visible', {
|
|
39
|
+
outline: 'none',
|
|
40
|
+
boxShadow: vars.effects.focusRing,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
globalStyle('.zylem-swatch[data-selected]', {
|
|
44
|
+
borderColor: '#F4FAFF',
|
|
45
|
+
boxShadow: `0 0 0 2px rgba(10, 20, 30, 0.9), 0 0 0 3px rgba(244, 250, 255, 0.85), ${vars.effects.glowPrimary}`,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
globalStyle('.zylem-swatch[disabled]', {
|
|
49
|
+
opacity: 0.45,
|
|
50
|
+
cursor: 'not-allowed',
|
|
51
|
+
});
|