@zylem/ui 0.8.11 → 0.8.13

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": "@zylem/ui",
3
- "version": "0.8.11",
3
+ "version": "0.8.13",
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": [
@@ -49,15 +53,6 @@
49
53
  "src",
50
54
  "vite"
51
55
  ],
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
- },
61
56
  "peerDependencies": {
62
57
  "solid-js": "^1.9.9"
63
58
  },
@@ -91,5 +86,12 @@
91
86
  "node": ">=22.12.0",
92
87
  "pnpm": ">=10.32.1"
93
88
  },
94
- "packageManager": "pnpm@10.33.0"
95
- }
89
+ "scripts": {
90
+ "build": "tsup",
91
+ "dev": "tsup --watch",
92
+ "showcase": "vite --host",
93
+ "test": "vitest run",
94
+ "test:watch": "vitest",
95
+ "lint": "biome check ."
96
+ }
97
+ }
@@ -1,59 +1,89 @@
1
1
  import { globalStyle } from '@vanilla-extract/css';
2
2
  import { vars } from '../../theme.css';
3
3
 
4
+ /** Soft drop plus a light top rim and a darker lower edge. */
5
+ const jewelDepth =
6
+ 'inset 0 1px 0 rgba(255,255,255,0.72), inset 0 -1px 0 rgba(0,0,0,0.28), 0 2px 4px rgba(0,0,0,0.4)';
7
+
4
8
  globalStyle('.zylem-window-controls', {
5
9
  display: 'flex',
6
10
  alignItems: 'center',
7
- gap: '7px',
11
+ flexShrink: 0,
12
+ gap: '6px',
8
13
  });
9
14
 
10
15
  /**
11
- * Shared glass icon button used for all panel chrome: window controls,
12
- * floating panels, and detached panels. `.floating-panel-button` is kept as
13
- * an alias so existing panel markup shares this single definition.
16
+ * Shared glass jewel used for all panel chrome: window controls, floating
17
+ * panels, and detached panels. `.floating-panel-button` is kept as an alias
18
+ * so existing panel markup shares this single definition.
19
+ *
20
+ * Collapse (and the untyped alias) is blue glass; close is red glass.
14
21
  */
15
22
  globalStyle('.zylem-window-control, .floating-panel-button', {
16
23
  all: 'unset',
17
24
  boxSizing: 'border-box',
25
+ position: 'relative',
18
26
  cursor: 'pointer',
19
- minWidth: '18px',
27
+ flexShrink: 0,
28
+ width: '28px',
20
29
  height: '18px',
21
- padding: '0 4px',
22
30
  display: 'inline-flex',
23
31
  alignItems: 'center',
24
32
  justifyContent: 'center',
25
- fontSize: '10px',
26
- lineHeight: 1,
27
- color: '#F4FAFF',
28
- textShadow: '0 1px 1px rgba(0,0,0,0.6)',
29
- borderRadius: vars.radii.control,
30
- border: '1px solid rgba(255,255,255,0.38)',
31
- background: vars.material.buttonGlass,
32
- boxShadow:
33
- 'inset 1px 1px 0 rgba(255,255,255,0.5), inset -1px -1px 0 rgba(0,0,0,0.4), 0 1px 3px rgba(0,0,0,0.4)',
34
- transition: `filter ${vars.motion.fast} ${vars.motion.easeOut}, transform ${vars.motion.fast} ${vars.motion.easeOut}, background ${vars.motion.fast} ${vars.motion.easeOut}, border-color ${vars.motion.fast} ${vars.motion.easeOut}`,
33
+ color: '#FFFFFF',
34
+ borderRadius: '6px',
35
+ border: '1px solid rgba(214, 234, 255, 0.82)',
36
+ background:
37
+ 'linear-gradient(180deg, rgba(206, 230, 255, 0.78) 0%, rgba(96, 158, 216, 0.84) 42%, rgba(48, 108, 172, 0.92) 100%)',
38
+ boxShadow: `${jewelDepth}, 0 0 8px rgba(97, 166, 232, 0.28)`,
39
+ transition: `filter ${vars.motion.fast} ${vars.motion.easeOut}, transform ${vars.motion.fast} ${vars.motion.easeOut}, background ${vars.motion.fast} ${vars.motion.easeOut}, border-color ${vars.motion.fast} ${vars.motion.easeOut}, box-shadow ${vars.motion.fast} ${vars.motion.easeOut}`,
40
+ });
41
+
42
+ /** Top-edge gloss. Clipped by the button; the icon sits above it. */
43
+ globalStyle('.zylem-window-control::before, .floating-panel-button::before', {
44
+ content: '""',
45
+ position: 'absolute',
46
+ left: '2px',
47
+ right: '2px',
48
+ top: '1px',
49
+ height: '48%',
50
+ borderRadius: '5px 5px 46% 46%',
51
+ background:
52
+ 'linear-gradient(180deg, rgba(255,255,255,0.55), rgba(255,255,255,0.1) 55%, rgba(255,255,255,0))',
53
+ pointerEvents: 'none',
54
+ });
55
+
56
+ globalStyle('.zylem-window-control svg, .floating-panel-button svg', {
57
+ position: 'relative',
58
+ zIndex: 1,
59
+ display: 'block',
60
+ filter: 'drop-shadow(0 1px 1px rgba(0,0,0,0.4))',
61
+ });
62
+
63
+ globalStyle('.zylem-window-control--close', {
64
+ borderColor: 'rgba(255, 198, 188, 0.88)',
65
+ background:
66
+ 'linear-gradient(180deg, rgba(255, 196, 186, 0.78) 0%, rgba(228, 82, 70, 0.9) 44%, rgba(186, 46, 40, 0.96) 100%)',
67
+ boxShadow: `${jewelDepth}, 0 0 8px rgba(230, 69, 52, 0.34)`,
35
68
  });
36
69
 
37
70
  globalStyle('.zylem-window-control:hover, .floating-panel-button:hover', {
38
- filter: 'brightness(1.2)',
71
+ filter: 'brightness(1.12)',
39
72
  transform: 'translateY(-1px)',
40
73
  });
41
74
 
42
75
  globalStyle('.zylem-window-control:active, .floating-panel-button:active', {
43
- filter: 'brightness(0.9)',
76
+ filter: 'brightness(0.92)',
44
77
  transform: 'translateY(1px)',
45
78
  });
46
79
 
47
80
  globalStyle(
48
81
  '.zylem-window-control:focus-visible, .floating-panel-button:focus-visible',
49
82
  {
50
- boxShadow: `inset 1px 1px 0 rgba(255,255,255,0.5), inset -1px -1px 0 rgba(0,0,0,0.4), ${vars.effects.focusRing}`,
83
+ boxShadow: `${jewelDepth}, 0 0 8px rgba(97, 166, 232, 0.28), ${vars.effects.focusRing}`,
51
84
  },
52
85
  );
53
86
 
54
- /** Close is neutral glass at rest and only turns accent on hover. */
55
- globalStyle('.zylem-window-control--close:hover', {
56
- background: vars.colors.accent,
57
- borderColor: 'rgba(255, 160, 150, 0.75)',
58
- boxShadow: `inset 1px 1px 0 rgba(255,255,255,0.4), inset -1px -1px 0 rgba(0,0,0,0.4), ${vars.effects.glowDanger}`,
87
+ globalStyle('.zylem-window-control--close:focus-visible', {
88
+ boxShadow: `${jewelDepth}, 0 0 8px rgba(230, 69, 52, 0.34), ${vars.effects.focusRing}`,
59
89
  });
@@ -1,6 +1,6 @@
1
1
  import { Show } from 'solid-js';
2
- import PanelBottomClose from 'lucide-solid/icons/panel-bottom-close';
3
- import PanelBottomOpen from 'lucide-solid/icons/panel-bottom-open';
2
+ import ChevronDown from 'lucide-solid/icons/chevron-down';
3
+ import ChevronUp from 'lucide-solid/icons/chevron-up';
4
4
  import X from 'lucide-solid/icons/x';
5
5
 
6
6
  export interface WindowControlsProps {
@@ -17,8 +17,8 @@ export interface WindowControlsProps {
17
17
  }
18
18
 
19
19
  /**
20
- * Shared glass window controls: the same icon buttons used by floating and
21
- * detached panels. Only controls with a handler are rendered.
20
+ * Shared glass window controls: blue collapse jewel and red close jewel, used
21
+ * by floating and detached panels. Only controls with a handler are rendered.
22
22
  */
23
23
  export function WindowControls(props: WindowControlsProps) {
24
24
  return (
@@ -32,7 +32,11 @@ export function WindowControls(props: WindowControlsProps) {
32
32
  title={props.collapsed ? 'Expand panel' : 'Collapse panel'}
33
33
  onClick={() => props.onCollapse?.()}
34
34
  >
35
- {props.collapsed ? <PanelBottomOpen size={12} /> : <PanelBottomClose size={12} />}
35
+ {props.collapsed ? (
36
+ <ChevronUp size={11} strokeWidth={2.5} />
37
+ ) : (
38
+ <ChevronDown size={11} strokeWidth={2.5} />
39
+ )}
36
40
  </button>
37
41
  </Show>
38
42
  <Show when={props.onClose}>
@@ -44,7 +48,7 @@ export function WindowControls(props: WindowControlsProps) {
44
48
  data-testid={props.closeTestId}
45
49
  onClick={() => props.onClose?.()}
46
50
  >
47
- <X size={12} />
51
+ <X size={11} strokeWidth={2.5} />
48
52
  </button>
49
53
  </Show>
50
54
  </div>
@@ -217,3 +217,20 @@ export {
217
217
  type TransportBarProps,
218
218
  type TransportTimeSignature,
219
219
  } from './TransportBar/TransportBar';
220
+
221
+ // Camera icons (Lucide-shaped originals; also `@zylem/ui/icons`)
222
+ export {
223
+ CAMERA_ICONS,
224
+ FreePerspective,
225
+ GameCamera,
226
+ IsometricPerspective,
227
+ Perspective,
228
+ SideviewPerspective,
229
+ TopDownPerspective,
230
+ cameraIconNodes,
231
+ createZylemIcon,
232
+ type CameraIcon,
233
+ type CameraIconName,
234
+ type IconNode,
235
+ type ZylemIconProps,
236
+ } from '../icons';
@@ -0,0 +1,71 @@
1
+ import { For, splitProps, type Component, type JSX } from 'solid-js';
2
+ import { Dynamic } from 'solid-js/web';
3
+ import type { IconNode } from './icon-nodes';
4
+
5
+ export interface ZylemIconProps extends JSX.SvgSVGAttributes<SVGSVGElement> {
6
+ color?: string;
7
+ size?: string | number;
8
+ strokeWidth?: string | number;
9
+ absoluteStrokeWidth?: boolean;
10
+ }
11
+
12
+ const DEFAULTS = {
13
+ xmlns: 'http://www.w3.org/2000/svg',
14
+ viewBox: '0 0 24 24',
15
+ fill: 'none',
16
+ 'stroke-linecap': 'round',
17
+ 'stroke-linejoin': 'round',
18
+ } as const;
19
+
20
+ function kebab(name: string): string {
21
+ return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
22
+ }
23
+
24
+ /**
25
+ * Solid icon factory matching lucide-solid's size / color / strokeWidth
26
+ * props so these glyphs drop into the same toolbar buttons and CSS.
27
+ */
28
+ export function createZylemIcon(
29
+ name: string,
30
+ iconNode: IconNode,
31
+ ): Component<ZylemIconProps> {
32
+ const Icon: Component<ZylemIconProps> = (props) => {
33
+ const [local, rest] = splitProps(props, [
34
+ 'color',
35
+ 'size',
36
+ 'strokeWidth',
37
+ 'class',
38
+ 'absoluteStrokeWidth',
39
+ ]);
40
+ const size = () => local.size ?? 24;
41
+ const strokeWidth = () => {
42
+ const width = Number(local.strokeWidth ?? 2);
43
+ if (local.absoluteStrokeWidth) {
44
+ return (width * 24) / Number(size());
45
+ }
46
+ return width;
47
+ };
48
+ const classes = () =>
49
+ ['zylem-icon', `zylem-icon-${kebab(name)}`, local.class]
50
+ .filter((value) => Boolean(value))
51
+ .join(' ');
52
+
53
+ return (
54
+ <svg
55
+ {...DEFAULTS}
56
+ width={size()}
57
+ height={size()}
58
+ stroke={local.color ?? 'currentColor'}
59
+ stroke-width={strokeWidth()}
60
+ class={classes()}
61
+ aria-hidden="true"
62
+ {...rest}
63
+ >
64
+ <For each={iconNode}>
65
+ {([element, attrs]) => <Dynamic component={element} {...attrs} />}
66
+ </For>
67
+ </svg>
68
+ );
69
+ };
70
+ return Icon;
71
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Original Zylem camera icons as Lucide-style SVG node lists.
3
+ * 24×24 viewBox, 2-unit stroke, no fill, rounded caps and joins.
4
+ */
5
+ export type IconNode = [
6
+ elementName: 'rect' | 'path',
7
+ attributes: Record<string, string>,
8
+ ][];
9
+
10
+ export const PerspectiveNode: IconNode = [
11
+ ['rect', { x: '2', y: '8', width: '8', height: '8', rx: '2' }],
12
+ ['path', { d: 'm10 10 4-2v8l-4-2' }],
13
+ ['path', { d: 'm18 7 4-3m-4 13 4 3' }],
14
+ ];
15
+
16
+ export const SideviewPerspectiveNode: IconNode = [
17
+ ['rect', { x: '2', y: '8', width: '8', height: '8', rx: '2' }],
18
+ ['path', { d: 'm10 10 4-2v8l-4-2' }],
19
+ ['path', { d: 'M18 6h4v12h-4' }],
20
+ ];
21
+
22
+ export const TopDownPerspectiveNode: IconNode = [
23
+ ['rect', { x: '8', y: '2', width: '8', height: '8', rx: '2' }],
24
+ ['path', { d: 'm10 10-2 4h8l-2-4' }],
25
+ ['path', { d: 'M6 18v4h12v-4' }],
26
+ ];
27
+
28
+ export const FreePerspectiveNode: IconNode = [
29
+ ['rect', { x: '7', y: '9', width: '7', height: '6', rx: '1' }],
30
+ ['path', { d: 'm14 11 3-2v6l-3-2' }],
31
+ ['path', { d: 'M3 8a9 9 0 0 1 15-3' }],
32
+ ['path', { d: 'M18 2v3h-3' }],
33
+ ['path', { d: 'M21 16a9 9 0 0 1-15 3' }],
34
+ ['path', { d: 'M6 22v-3h3' }],
35
+ ];
36
+
37
+ /** Source: `src/icons/svg/camera-isometric.svg` */
38
+ export const IsometricPerspectiveNode: IconNode = [
39
+ ['path', { d: 'M11.549 11.368 2 5.855' }],
40
+ ['path', { d: 'M11.549 19.944 11.549 17.252' }],
41
+ ['path', { d: 'M11.549 14.06 11.549 11.368' }],
42
+ ['path', { d: 'M11.549 19.944 2 14.431' }],
43
+ ['path', { d: 'M2 14.431 2 5.855' }],
44
+ ['path', { d: 'M2 5.855 6.775 3.098' }],
45
+ ['path', { d: 'M6.775 3.098 16.324 8.611' }],
46
+ ['path', { d: 'M11.549 11.368 16.324 8.611' }],
47
+ ['path', { d: 'M16.324 8.611 16.324 11.151' }],
48
+ ['path', { d: 'M13.749 18.674 11.549 19.944' }],
49
+ ['path', { d: 'M15.887 10.899 21.192 13.962' }],
50
+ ['path', { d: 'M11.986 17.656 17.291 20.719' }],
51
+ ['path', { d: 'M15.887 10.899A3.901 2.252 -60 0 0 11.986 17.656' }],
52
+ [
53
+ 'path',
54
+ {
55
+ d: 'M21.192 13.962A3.901 2.252 -60 1 1 17.291 20.719A3.901 2.252 -60 1 1 21.192 13.962Z',
56
+ },
57
+ ],
58
+ ];
59
+ export const GameCameraNode: IconNode = [
60
+ ['rect', { x: '2', y: '6', width: '16', height: '12', rx: '2' }],
61
+ ['path', { d: 'm18 10 4-3v10l-4-3' }],
62
+ ['path', { d: 'M6 12h4m-2-2v4' }],
63
+ ['path', { d: 'M14 10h.01M14 14h.01' }],
64
+ ];
65
+
66
+ export const cameraIconNodes = {
67
+ perspective: PerspectiveNode,
68
+ 'sideview-perspective': SideviewPerspectiveNode,
69
+ 'top-down-perspective': TopDownPerspectiveNode,
70
+ 'isometric-perspective': IsometricPerspectiveNode,
71
+ 'free-perspective': FreePerspectiveNode,
72
+ 'game-camera': GameCameraNode,
73
+ } as const;
74
+
75
+ export type CameraIconName = keyof typeof cameraIconNodes;
@@ -0,0 +1,54 @@
1
+ import type { Component } from 'solid-js';
2
+ import { createZylemIcon, type ZylemIconProps } from './create-icon';
3
+ import {
4
+ FreePerspectiveNode,
5
+ GameCameraNode,
6
+ IsometricPerspectiveNode,
7
+ PerspectiveNode,
8
+ SideviewPerspectiveNode,
9
+ TopDownPerspectiveNode,
10
+ } from './icon-nodes';
11
+
12
+ export type { IconNode, CameraIconName } from './icon-nodes';
13
+ export {
14
+ cameraIconNodes,
15
+ FreePerspectiveNode,
16
+ GameCameraNode,
17
+ IsometricPerspectiveNode,
18
+ PerspectiveNode,
19
+ SideviewPerspectiveNode,
20
+ TopDownPerspectiveNode,
21
+ } from './icon-nodes';
22
+ export { createZylemIcon, type ZylemIconProps } from './create-icon';
23
+
24
+ export type CameraIcon = Component<ZylemIconProps>;
25
+
26
+ /** Recorder with diverging FOV rays. */
27
+ export const Perspective = createZylemIcon('Perspective', PerspectiveNode);
28
+ /** Recorder facing a vertical view plane (side / ortho). */
29
+ export const SideviewPerspective = createZylemIcon(
30
+ 'SideviewPerspective',
31
+ SideviewPerspectiveNode,
32
+ );
33
+ /** Downward recorder facing a horizontal plane (top-down). */
34
+ export const TopDownPerspective = createZylemIcon(
35
+ 'TopDownPerspective',
36
+ TopDownPerspectiveNode,
37
+ );
38
+ /** Isometric camera body with lens (from camera-isometric.svg). */
39
+ export const IsometricPerspective = createZylemIcon(
40
+ 'IsometricPerspective',
41
+ IsometricPerspectiveNode,
42
+ );/** Recorder with two orbit arrows (free / orbit camera). */
43
+ export const FreePerspective = createZylemIcon('FreePerspective', FreePerspectiveNode);
44
+ /** Recorder with controller buttons (game camera). */
45
+ export const GameCamera = createZylemIcon('GameCamera', GameCameraNode);
46
+
47
+ export const CAMERA_ICONS = [
48
+ { name: 'perspective', label: 'Perspective', Icon: Perspective },
49
+ { name: 'sideview-perspective', label: 'Side view', Icon: SideviewPerspective },
50
+ { name: 'top-down-perspective', label: 'Top down', Icon: TopDownPerspective },
51
+ { name: 'isometric-perspective', label: 'Isometric', Icon: IsometricPerspective },
52
+ { name: 'free-perspective', label: 'Free / orbit', Icon: FreePerspective },
53
+ { name: 'game-camera', label: 'Game camera', Icon: GameCamera },
54
+ ] as const;
@@ -0,0 +1,16 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M11.549 11.368 2 5.855" />
3
+ <path d="M11.549 19.944 11.549 17.252" />
4
+ <path d="M11.549 14.06 11.549 11.368" />
5
+ <path d="M11.549 19.944 2 14.431" />
6
+ <path d="M2 14.431 2 5.855" />
7
+ <path d="M2 5.855 6.775 3.098" />
8
+ <path d="M6.775 3.098 16.324 8.611" />
9
+ <path d="M11.549 11.368 16.324 8.611" />
10
+ <path d="M16.324 8.611 16.324 11.151" />
11
+ <path d="M13.749 18.674 11.549 19.944" />
12
+ <path d="M15.887 10.899 21.192 13.962" />
13
+ <path d="M11.986 17.656 17.291 20.719" />
14
+ <path d="M15.887 10.899A3.901 2.252 -60 0 0 11.986 17.656" />
15
+ <path d="M21.192 13.962A3.901 2.252 -60 1 1 17.291 20.719A3.901 2.252 -60 1 1 21.192 13.962Z" />
16
+ </svg>
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="7" y="9" width="7" height="6" rx="1" />
3
+ <path d="m14 11 3-2v6l-3-2" />
4
+ <path d="M3 8a9 9 0 0 1 15-3" />
5
+ <path d="M18 2v3h-3" />
6
+ <path d="M21 16a9 9 0 0 1-15 3" />
7
+ <path d="M6 22v-3h3" />
8
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="2" y="6" width="16" height="12" rx="2" />
3
+ <path d="m18 10 4-3v10l-4-3" />
4
+ <path d="M6 12h4m-2-2v4" />
5
+ <path d="M14 10h.01M14 14h.01" />
6
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="2" y="8" width="8" height="8" rx="2" />
3
+ <path d="m10 10 4-2v8l-4-2" />
4
+ <path d="m18 7 4-3m-4 13 4 3" />
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="2" y="8" width="8" height="8" rx="2" />
3
+ <path d="m10 10 4-2v8l-4-2" />
4
+ <path d="M18 6h4v12h-4" />
5
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <rect x="8" y="2" width="8" height="8" rx="2" />
3
+ <path d="m10 10-2 4h8l-2-4" />
4
+ <path d="M6 18v4h12v-4" />
5
+ </svg>