@zylem/ui 0.9.3 → 0.9.5
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 +1 -1
- package/dist/styles.css +1 -1
- package/package.json +12 -10
- package/src/components/BuildLabel/resolve-build-version.ts +9 -0
- package/src/components/DropdownMenu/DropdownMenu.css.ts +1 -1
- package/src/components/DropdownMenu/DropdownMenu.tsx +14 -1
- package/src/components/MenuButton/MenuButton.css.ts +12 -0
- package/src/components/TitleBar/TitleBar.tsx +4 -0
- package/src/components/ToolbarButton/ToolbarButton.tsx +32 -4
- package/src/components/ToolbarDropdownMenu/ToolbarDropdownMenu.tsx +61 -0
- package/src/components/Tooltip/Tooltip.css.ts +9 -0
- package/src/components/WindowControls/WindowControls.tsx +33 -3
- package/src/components/index.ts +10 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zylem/ui",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Design tokens, global styles, and Solid components for Zylem, built with vanilla-extract",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -53,6 +53,15 @@
|
|
|
53
53
|
"src",
|
|
54
54
|
"vite"
|
|
55
55
|
],
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsup",
|
|
58
|
+
"prepublishOnly": "NODE_ENV=production tsup",
|
|
59
|
+
"dev": "tsup --watch",
|
|
60
|
+
"showcase": "vite --host",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"lint": "biome check ."
|
|
64
|
+
},
|
|
56
65
|
"peerDependencies": {
|
|
57
66
|
"solid-js": "^1.9.9"
|
|
58
67
|
},
|
|
@@ -86,12 +95,5 @@
|
|
|
86
95
|
"node": ">=22.12.0",
|
|
87
96
|
"pnpm": ">=10.32.1"
|
|
88
97
|
},
|
|
89
|
-
"
|
|
90
|
-
|
|
91
|
-
"dev": "tsup --watch",
|
|
92
|
-
"showcase": "vite --host",
|
|
93
|
-
"test": "vitest run",
|
|
94
|
-
"test:watch": "vitest",
|
|
95
|
-
"lint": "biome check ."
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
+
"packageManager": "pnpm@10.33.0"
|
|
99
|
+
}
|
|
@@ -3,6 +3,11 @@ export interface BuildLabelModel {
|
|
|
3
3
|
version?: string;
|
|
4
4
|
/** Resolved `@zylem/*` package versions collected at build time. */
|
|
5
5
|
packages?: Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Package name to show when it is present in `packages`. Demo apps leave
|
|
8
|
+
* this unset and keep the engine-first default.
|
|
9
|
+
*/
|
|
10
|
+
prefer?: string;
|
|
6
11
|
}
|
|
7
12
|
|
|
8
13
|
const PREFERRED_PACKAGES = [
|
|
@@ -21,6 +26,10 @@ const PREFERRED_PACKAGES = [
|
|
|
21
26
|
*/
|
|
22
27
|
export function resolveBuildVersion(model: BuildLabelModel): string {
|
|
23
28
|
const packages = model.packages;
|
|
29
|
+
if (packages && model.prefer) {
|
|
30
|
+
const preferred = packages[model.prefer];
|
|
31
|
+
if (preferred) return preferred;
|
|
32
|
+
}
|
|
24
33
|
if (packages) {
|
|
25
34
|
for (const name of PREFERRED_PACKAGES) {
|
|
26
35
|
const version = packages[name];
|
|
@@ -17,6 +17,11 @@ export interface DropdownMenuItemConfig {
|
|
|
17
17
|
* one-of-many the way a stage picker works).
|
|
18
18
|
*/
|
|
19
19
|
checked?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Close the menu when this item is chosen. Checkable items stay open unless
|
|
22
|
+
* this is set, so a one-of-many picker can dismiss itself.
|
|
23
|
+
*/
|
|
24
|
+
closeOnSelect?: boolean;
|
|
20
25
|
/** Render a separator line above this item. */
|
|
21
26
|
separatorAbove?: boolean;
|
|
22
27
|
}
|
|
@@ -28,6 +33,8 @@ export interface DropdownMenuProps {
|
|
|
28
33
|
placement?: 'bottom-start' | 'bottom-end' | 'bottom' | 'top-start' | 'top-end' | 'top' | 'right-start' | 'left-start';
|
|
29
34
|
/** Class applied to the trigger button. */
|
|
30
35
|
triggerClass?: string;
|
|
36
|
+
/** Accessible name when the trigger content is only an icon. */
|
|
37
|
+
triggerLabel?: string;
|
|
31
38
|
}
|
|
32
39
|
|
|
33
40
|
/**
|
|
@@ -85,6 +92,7 @@ export function MenuItems(props: { items: DropdownMenuItemConfig[] }) {
|
|
|
85
92
|
class={itemClass(item)}
|
|
86
93
|
checked={item.checked}
|
|
87
94
|
disabled={item.disabled}
|
|
95
|
+
closeOnSelect={item.closeOnSelect}
|
|
88
96
|
onSelect={item.onSelect}
|
|
89
97
|
>
|
|
90
98
|
{body(item)}
|
|
@@ -108,7 +116,12 @@ export function DropdownMenu(props: DropdownMenuProps) {
|
|
|
108
116
|
|
|
109
117
|
return (
|
|
110
118
|
<KDropdownMenu.Root placement={props.placement ?? 'bottom-start'}>
|
|
111
|
-
<KDropdownMenu.Trigger
|
|
119
|
+
<KDropdownMenu.Trigger
|
|
120
|
+
ref={layer.anchorRef}
|
|
121
|
+
class={props.triggerClass}
|
|
122
|
+
aria-label={props.triggerLabel}
|
|
123
|
+
title={props.triggerLabel}
|
|
124
|
+
>
|
|
112
125
|
{props.trigger}
|
|
113
126
|
</KDropdownMenu.Trigger>
|
|
114
127
|
<Show when={layer.mount()} keyed>
|
|
@@ -92,3 +92,15 @@ globalStyle('.zylem-menu-button__face svg', {
|
|
|
92
92
|
width: '18px',
|
|
93
93
|
height: '18px',
|
|
94
94
|
});
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Vertical column of icon buttons inside the panel. The panel chrome stays on
|
|
98
|
+
* `.zylem-menu-button__panel`; this only stacks and pads its contents.
|
|
99
|
+
*/
|
|
100
|
+
globalStyle('.zylem-menu-button__stack', {
|
|
101
|
+
display: 'flex',
|
|
102
|
+
flexDirection: 'column',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
gap: vars.spacing.xs,
|
|
105
|
+
padding: vars.spacing.sm,
|
|
106
|
+
});
|
|
@@ -33,6 +33,8 @@ export function TitleBar(props: TitleBarProps) {
|
|
|
33
33
|
'collapsed',
|
|
34
34
|
'closeLabel',
|
|
35
35
|
'closeTestId',
|
|
36
|
+
'dock',
|
|
37
|
+
'onDock',
|
|
36
38
|
'class',
|
|
37
39
|
]);
|
|
38
40
|
|
|
@@ -60,6 +62,8 @@ export function TitleBar(props: TitleBarProps) {
|
|
|
60
62
|
collapsed={local.collapsed}
|
|
61
63
|
closeLabel={local.closeLabel}
|
|
62
64
|
closeTestId={local.closeTestId}
|
|
65
|
+
dock={local.dock}
|
|
66
|
+
onDock={local.onDock}
|
|
63
67
|
/>
|
|
64
68
|
</div>
|
|
65
69
|
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Button as KButton, Tooltip as KTooltip } from '@kobalte/core';
|
|
2
|
-
import { Show, type JSX } from 'solid-js';
|
|
2
|
+
import { Show, createSignal, onMount, type JSX } from 'solid-js';
|
|
3
3
|
import { useLayer } from '../../layers';
|
|
4
4
|
|
|
5
5
|
export interface ToolbarButtonProps {
|
|
@@ -17,6 +17,15 @@ export interface ToolbarButtonProps {
|
|
|
17
17
|
class?: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* A floating popup (menu, popover) wraps its panel in a popper positioner.
|
|
22
|
+
* Tooltips inside one open beside the button, because a vertical stack has no
|
|
23
|
+
* free room above or below an item.
|
|
24
|
+
*/
|
|
25
|
+
function insidePopup(element: Element | undefined): boolean {
|
|
26
|
+
return Boolean(element?.closest('[data-popper-positioner]'));
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* HyperGlass toolbar button: square glass icon button with a tooltip.
|
|
22
31
|
* Selected and pressed states render as a live-green jewel.
|
|
@@ -26,10 +35,27 @@ export interface ToolbarButtonProps {
|
|
|
26
35
|
*/
|
|
27
36
|
export function ToolbarButton(props: ToolbarButtonProps) {
|
|
28
37
|
const layer = useLayer('tooltip');
|
|
38
|
+
const [beside, setBeside] = createSignal(false);
|
|
39
|
+
let trigger: HTMLElement | undefined;
|
|
40
|
+
|
|
41
|
+
const syncPlacement = () => setBeside(insidePopup(trigger));
|
|
42
|
+
onMount(syncPlacement);
|
|
29
43
|
|
|
30
44
|
return (
|
|
31
|
-
<KTooltip.Root
|
|
32
|
-
|
|
45
|
+
<KTooltip.Root
|
|
46
|
+
openDelay={400}
|
|
47
|
+
placement={beside() ? 'right' : undefined}
|
|
48
|
+
flip={beside() ? 'left' : undefined}
|
|
49
|
+
gutter={beside() ? 8 : undefined}
|
|
50
|
+
>
|
|
51
|
+
<KTooltip.Trigger
|
|
52
|
+
as="span"
|
|
53
|
+
ref={(element: HTMLElement) => {
|
|
54
|
+
trigger = element;
|
|
55
|
+
layer.anchorRef(element);
|
|
56
|
+
}}
|
|
57
|
+
onPointerEnter={syncPlacement}
|
|
58
|
+
>
|
|
33
59
|
<KButton.Root
|
|
34
60
|
aria-label={props.label}
|
|
35
61
|
aria-pressed={props.pressed}
|
|
@@ -48,7 +74,9 @@ export function ToolbarButton(props: ToolbarButtonProps) {
|
|
|
48
74
|
<Show when={layer.mount()} keyed>
|
|
49
75
|
{(mount) => (
|
|
50
76
|
<KTooltip.Portal mount={mount}>
|
|
51
|
-
<KTooltip.Content
|
|
77
|
+
<KTooltip.Content
|
|
78
|
+
class={beside() ? 'zylem-tooltip zylem-tooltip--front' : 'zylem-tooltip'}
|
|
79
|
+
>
|
|
52
80
|
{props.label}
|
|
53
81
|
</KTooltip.Content>
|
|
54
82
|
</KTooltip.Portal>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { For, Show, type Component } from 'solid-js';
|
|
2
|
+
import { Dynamic } from 'solid-js/web';
|
|
3
|
+
import { MenuButton, type MenuButtonProps } from '../MenuButton/MenuButton';
|
|
4
|
+
import { ToolbarButton } from '../ToolbarButton/ToolbarButton';
|
|
5
|
+
|
|
6
|
+
export interface ToolbarDropdownItem {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
/** Glyph for the face and for this row in the menu. */
|
|
10
|
+
icon: Component<{ class?: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ToolbarDropdownMenuProps {
|
|
14
|
+
/** Choices. The face shows whichever matches `value`. */
|
|
15
|
+
items: readonly ToolbarDropdownItem[];
|
|
16
|
+
value: string;
|
|
17
|
+
onChange: (id: string) => void;
|
|
18
|
+
/** Where the menu opens. Defaults to dropping down from the face. */
|
|
19
|
+
placement?: MenuButtonProps['placement'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A toolbar button that shows the current choice and opens a vertical menu of
|
|
24
|
+
* the others. The face is the selected item; the rows are plain icon buttons,
|
|
25
|
+
* and picking one updates the face and closes the menu.
|
|
26
|
+
*/
|
|
27
|
+
export function ToolbarDropdownMenu(props: ToolbarDropdownMenuProps) {
|
|
28
|
+
const current = () =>
|
|
29
|
+
props.items.find((item) => item.id === props.value) ?? props.items[0];
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Show when={current()} keyed>
|
|
33
|
+
{(item) => (
|
|
34
|
+
<MenuButton
|
|
35
|
+
label={item.label}
|
|
36
|
+
selected
|
|
37
|
+
placement={props.placement ?? 'bottom-start'}
|
|
38
|
+
menu={(close) => (
|
|
39
|
+
<div class="zylem-menu-button__stack">
|
|
40
|
+
<For each={props.items}>
|
|
41
|
+
{(entry) => (
|
|
42
|
+
<ToolbarButton
|
|
43
|
+
label={entry.label}
|
|
44
|
+
onClick={() => {
|
|
45
|
+
props.onChange(entry.id);
|
|
46
|
+
close();
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<Dynamic component={entry.icon} class="zylem-icon" />
|
|
50
|
+
</ToolbarButton>
|
|
51
|
+
)}
|
|
52
|
+
</For>
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
<Dynamic component={item.icon} class="zylem-icon" />
|
|
57
|
+
</MenuButton>
|
|
58
|
+
)}
|
|
59
|
+
</Show>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -33,3 +33,12 @@ globalStyle('.zylem-tooltip', {
|
|
|
33
33
|
globalStyle('.zylem-tooltip[data-enter]', {
|
|
34
34
|
animation: `${enter} 0.15s ${vars.motion.easeOut}`,
|
|
35
35
|
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Opened from inside a popup. The menu tier sits above tooltips, so one step
|
|
39
|
+
* past it is what puts this label over the popup it came from and over
|
|
40
|
+
* everything else.
|
|
41
|
+
*/
|
|
42
|
+
globalStyle('.zylem-tooltip.zylem-tooltip--front', {
|
|
43
|
+
zIndex: `calc(${vars.layers.menu} + 1)`,
|
|
44
|
+
});
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { Show } from 'solid-js';
|
|
2
2
|
import ChevronDown from 'lucide-solid/icons/chevron-down';
|
|
3
3
|
import ChevronUp from 'lucide-solid/icons/chevron-up';
|
|
4
|
+
import Dock from 'lucide-solid/icons/dock';
|
|
4
5
|
import X from 'lucide-solid/icons/x';
|
|
6
|
+
import { DropdownMenu } from '../DropdownMenu/DropdownMenu';
|
|
7
|
+
|
|
8
|
+
/** Where a panel sits. `float` leaves it free of the top and bottom edges. */
|
|
9
|
+
export type DockPosition = 'top' | 'float' | 'bottom';
|
|
10
|
+
|
|
11
|
+
const DOCK_OPTIONS: readonly { id: DockPosition; label: string }[] = [
|
|
12
|
+
{ id: 'top', label: 'Top' },
|
|
13
|
+
{ id: 'float', label: 'Float' },
|
|
14
|
+
{ id: 'bottom', label: 'Bottom' },
|
|
15
|
+
];
|
|
5
16
|
|
|
6
17
|
export interface WindowControlsProps {
|
|
7
18
|
// Optional props accept an explicit `undefined` so callers compiled with
|
|
@@ -14,17 +25,36 @@ export interface WindowControlsProps {
|
|
|
14
25
|
closeLabel?: string | undefined;
|
|
15
26
|
/** Forwarded as data-testid on the close button. */
|
|
16
27
|
closeTestId?: string | undefined;
|
|
28
|
+
/** Current dock, checked in the menu. Omit for none checked. */
|
|
29
|
+
dock?: DockPosition | undefined;
|
|
30
|
+
/** Renders the dock control. Called with the position chosen from the menu. */
|
|
31
|
+
onDock?: ((position: DockPosition) => void) | undefined;
|
|
17
32
|
}
|
|
18
33
|
|
|
19
34
|
/**
|
|
20
35
|
* Shared ghost window controls, used by floating and detached panels. Close
|
|
21
|
-
* takes the danger color only while hovered or focused.
|
|
22
|
-
* handler are rendered.
|
|
36
|
+
* takes the danger color only while hovered or focused. Dock opens a menu of
|
|
37
|
+
* where the panel sits. Only controls with a handler are rendered.
|
|
23
38
|
*/
|
|
24
39
|
export function WindowControls(props: WindowControlsProps) {
|
|
25
40
|
return (
|
|
26
|
-
<Show when={props.onClose || props.onCollapse}>
|
|
41
|
+
<Show when={props.onClose || props.onCollapse || props.onDock}>
|
|
27
42
|
<div class="zylem-window-controls" role="group" aria-label="Window controls">
|
|
43
|
+
<Show when={props.onDock}>
|
|
44
|
+
<DropdownMenu
|
|
45
|
+
triggerLabel="Dock panel"
|
|
46
|
+
triggerClass="zylem-window-control zylem-window-control--dock"
|
|
47
|
+
placement="bottom-end"
|
|
48
|
+
trigger={<Dock size={11} strokeWidth={2.5} aria-hidden="true" />}
|
|
49
|
+
items={DOCK_OPTIONS.map((option) => ({
|
|
50
|
+
id: option.id,
|
|
51
|
+
label: option.label,
|
|
52
|
+
checked: props.dock === option.id,
|
|
53
|
+
closeOnSelect: true,
|
|
54
|
+
onSelect: () => props.onDock?.(option.id),
|
|
55
|
+
}))}
|
|
56
|
+
/>
|
|
57
|
+
</Show>
|
|
28
58
|
<Show when={props.onCollapse}>
|
|
29
59
|
<button
|
|
30
60
|
type="button"
|
package/src/components/index.ts
CHANGED
|
@@ -68,7 +68,11 @@ export { Toast, type ToastProps, type ToastTone } from './Toast/Toast';
|
|
|
68
68
|
export { Separator, type SeparatorProps } from './Separator/Separator';
|
|
69
69
|
|
|
70
70
|
// Composites
|
|
71
|
-
export {
|
|
71
|
+
export {
|
|
72
|
+
WindowControls,
|
|
73
|
+
type DockPosition,
|
|
74
|
+
type WindowControlsProps,
|
|
75
|
+
} from './WindowControls/WindowControls';
|
|
72
76
|
export { TitleBar, type TitleBarProps } from './TitleBar/TitleBar';
|
|
73
77
|
export { Panel, type PanelProps } from './Panel/Panel';
|
|
74
78
|
export { Toolbar, type ToolbarProps } from './Toolbar/Toolbar';
|
|
@@ -90,6 +94,11 @@ export {
|
|
|
90
94
|
} from './Sidebar/Sidebar';
|
|
91
95
|
export { Card, type CardProps } from './Card/Card';
|
|
92
96
|
export { MenuButton, type MenuButtonProps } from './MenuButton/MenuButton';
|
|
97
|
+
export {
|
|
98
|
+
ToolbarDropdownMenu,
|
|
99
|
+
type ToolbarDropdownItem,
|
|
100
|
+
type ToolbarDropdownMenuProps,
|
|
101
|
+
} from './ToolbarDropdownMenu/ToolbarDropdownMenu';
|
|
93
102
|
export { ItemPicker, type ItemPickerProps } from './ItemPicker/ItemPicker';
|
|
94
103
|
export {
|
|
95
104
|
filterItems,
|