@zylem/ui 0.1.1 → 0.2.3
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-3TP2SNNW.js +1 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.js +1 -1
- package/package.json +14 -5
- package/src/components/Button/Button.css.ts +3 -0
- package/src/components/Console/Console.css.ts +4 -2
- package/src/components/Dialog/Dialog.css.ts +3 -2
- package/src/components/Dialog/Dialog.tsx +44 -20
- package/src/components/DropdownMenu/DropdownMenu.css.ts +1 -1
- package/src/components/DropdownMenu/DropdownMenu.tsx +47 -28
- package/src/components/ItemPicker/ItemPicker.css.ts +101 -0
- package/src/components/ItemPicker/ItemPicker.tsx +108 -0
- package/src/components/ItemPicker/filter-items.ts +75 -0
- package/src/components/MenuButton/MenuButton.css.ts +91 -0
- package/src/components/MenuButton/MenuButton.tsx +153 -0
- package/src/components/Property/Property.tsx +8 -6
- package/src/components/SearchInput/SearchInput.tsx +3 -0
- package/src/components/Select/Select.css.ts +1 -1
- package/src/components/Select/Select.tsx +17 -5
- package/src/components/ToolbarButton/ToolbarButton.css.ts +3 -0
- package/src/components/ToolbarButton/ToolbarButton.tsx +17 -7
- package/src/components/Tooltip/Tooltip.css.ts +4 -1
- package/src/components/Tooltip/Tooltip.tsx +26 -8
- package/src/components/WindowControls/WindowControls.tsx +7 -5
- package/src/components/index.ts +27 -0
- package/src/global/detached-panel.css.ts +4 -1
- package/src/global/editor-base.css.ts +32 -10
- package/src/global/hyperglass-base.css.ts +80 -5
- package/src/layers/LayerContext.tsx +132 -0
- package/src/layers/dismiss-guard.ts +81 -0
- package/src/layers/index.ts +27 -0
- package/src/layers/layer-tokens.css.ts +38 -0
- package/src/layers/resolve-mount.ts +48 -0
- package/src/layers/tiers.ts +53 -0
- package/src/styles.ts +3 -0
- package/src/theme.css.ts +7 -0
- package/dist/chunk-VGOOWBSO.js +0 -1
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Popover as KPopover } from '@kobalte/core';
|
|
2
|
+
import { Show, createSignal, type JSX } from 'solid-js';
|
|
3
|
+
import { createDismissGuard, useLayer } from '../../layers';
|
|
4
|
+
import { Tooltip } from '../Tooltip/Tooltip';
|
|
5
|
+
|
|
6
|
+
export interface MenuButtonProps {
|
|
7
|
+
/** Accessible name, and the tooltip text unless `tooltip` overrides it. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Button face content, usually an icon. */
|
|
10
|
+
children: JSX.Element;
|
|
11
|
+
/** Panel content: a menu, a picker, anything. Rendered only while open. */
|
|
12
|
+
menu: (close: () => void) => JSX.Element;
|
|
13
|
+
/**
|
|
14
|
+
* Primary action for the face. Given one, the button splits: the face fires
|
|
15
|
+
* this and the chevron opens the menu. Without one, the whole button opens
|
|
16
|
+
* the menu and the chevron is only there to say so.
|
|
17
|
+
*
|
|
18
|
+
* Optional props here accept an explicit `undefined` so a caller compiled with
|
|
19
|
+
* `exactOptionalPropertyTypes` can decide the split at the call site.
|
|
20
|
+
*/
|
|
21
|
+
onAction?: (() => void) | undefined;
|
|
22
|
+
/** Renders the face in its selected state. */
|
|
23
|
+
selected?: boolean | undefined;
|
|
24
|
+
disabled?: boolean | undefined;
|
|
25
|
+
/** Tooltip text; defaults to `label`. Pass `null` for no tooltip. */
|
|
26
|
+
tooltip?: string | null | undefined;
|
|
27
|
+
placement?:
|
|
28
|
+
| 'bottom-start'
|
|
29
|
+
| 'bottom-end'
|
|
30
|
+
| 'bottom'
|
|
31
|
+
| 'top-start'
|
|
32
|
+
| 'top-end'
|
|
33
|
+
| 'top'
|
|
34
|
+
| undefined;
|
|
35
|
+
/** Controlled open state. Omit to let the button own it. */
|
|
36
|
+
open?: boolean | undefined;
|
|
37
|
+
onOpenChange?: ((open: boolean) => void) | undefined;
|
|
38
|
+
class?: string | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Shown in both modes, because a chevron means "this has a menu" and that is
|
|
43
|
+
* true whether or not the face carries its own action.
|
|
44
|
+
*/
|
|
45
|
+
const CHEVRON = '▾';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A toolbar button that owns a menu, optionally split.
|
|
49
|
+
*
|
|
50
|
+
* Split mode exists because choosing and doing are separate jobs: the face
|
|
51
|
+
* repeats the last choice and the chevron is there for picking a different one.
|
|
52
|
+
* Placing ten of the same thing is then one trip through the menu and ten
|
|
53
|
+
* clicks, rather than ten trips.
|
|
54
|
+
*
|
|
55
|
+
* The panel is layered at the `menu` tier and portaled out of the button's own
|
|
56
|
+
* subtree, so a toolbar that clips its overflow or sits in a lower stacking
|
|
57
|
+
* context cannot hide it.
|
|
58
|
+
*/
|
|
59
|
+
export function MenuButton(props: MenuButtonProps) {
|
|
60
|
+
const [uncontrolledOpen, setUncontrolledOpen] = createSignal(false);
|
|
61
|
+
const layer = useLayer('menu');
|
|
62
|
+
const dismiss = createDismissGuard();
|
|
63
|
+
|
|
64
|
+
const isOpen = () => props.open ?? uncontrolledOpen();
|
|
65
|
+
const setOpen = (open: boolean) => {
|
|
66
|
+
setUncontrolledOpen(open);
|
|
67
|
+
props.onOpenChange?.(open);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const isSplit = () => props.onAction !== undefined;
|
|
71
|
+
const tooltipText = () =>
|
|
72
|
+
props.tooltip === null ? null : props.tooltip ?? props.label;
|
|
73
|
+
|
|
74
|
+
/** Wrap in a tooltip only when there is text for one. */
|
|
75
|
+
const described = (content: JSX.Element) => (
|
|
76
|
+
<Show when={tooltipText()} fallback={content} keyed>
|
|
77
|
+
{(text) => <Tooltip content={text}>{content}</Tooltip>}
|
|
78
|
+
</Show>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
class={props.class ? `zylem-menu-button ${props.class}` : 'zylem-menu-button'}
|
|
84
|
+
data-split={isSplit() ? '' : undefined}
|
|
85
|
+
>
|
|
86
|
+
<KPopover.Root
|
|
87
|
+
open={isOpen()}
|
|
88
|
+
onOpenChange={setOpen}
|
|
89
|
+
placement={props.placement ?? 'bottom-start'}
|
|
90
|
+
>
|
|
91
|
+
<Show
|
|
92
|
+
when={isSplit()}
|
|
93
|
+
fallback={described(
|
|
94
|
+
<KPopover.Trigger
|
|
95
|
+
ref={layer.anchorRef}
|
|
96
|
+
class="zylem-toolbar-btn zylem-menu-button__face"
|
|
97
|
+
aria-label={props.label}
|
|
98
|
+
disabled={props.disabled}
|
|
99
|
+
data-selected={props.selected ? '' : undefined}
|
|
100
|
+
>
|
|
101
|
+
{props.children}
|
|
102
|
+
{/*
|
|
103
|
+
* Signage only: the button around it already opens the menu, so
|
|
104
|
+
* this must not read as a second control.
|
|
105
|
+
*/}
|
|
106
|
+
<span
|
|
107
|
+
class="zylem-menu-button__chevron zylem-menu-button__chevron--static"
|
|
108
|
+
aria-hidden="true"
|
|
109
|
+
>
|
|
110
|
+
{CHEVRON}
|
|
111
|
+
</span>
|
|
112
|
+
</KPopover.Trigger>,
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
{described(
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
ref={layer.anchorRef}
|
|
119
|
+
class="zylem-toolbar-btn zylem-menu-button__face"
|
|
120
|
+
aria-label={props.label}
|
|
121
|
+
disabled={props.disabled}
|
|
122
|
+
data-selected={props.selected ? '' : undefined}
|
|
123
|
+
onClick={() => props.onAction?.()}
|
|
124
|
+
>
|
|
125
|
+
{props.children}
|
|
126
|
+
</button>,
|
|
127
|
+
)}
|
|
128
|
+
<KPopover.Trigger
|
|
129
|
+
class="zylem-menu-button__chevron"
|
|
130
|
+
aria-label={`${props.label} options`}
|
|
131
|
+
disabled={props.disabled}
|
|
132
|
+
>
|
|
133
|
+
<span aria-hidden="true">{CHEVRON}</span>
|
|
134
|
+
</KPopover.Trigger>
|
|
135
|
+
</Show>
|
|
136
|
+
|
|
137
|
+
<Show when={layer.mount()} keyed>
|
|
138
|
+
{(mount) => (
|
|
139
|
+
<KPopover.Portal mount={mount}>
|
|
140
|
+
<KPopover.Content
|
|
141
|
+
ref={dismiss.ref}
|
|
142
|
+
class="zylem-menu-button__panel"
|
|
143
|
+
onInteractOutside={dismiss.onInteractOutside}
|
|
144
|
+
>
|
|
145
|
+
{props.menu(() => setOpen(false))}
|
|
146
|
+
</KPopover.Content>
|
|
147
|
+
</KPopover.Portal>
|
|
148
|
+
)}
|
|
149
|
+
</Show>
|
|
150
|
+
</KPopover.Root>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -25,17 +25,19 @@ export function PropertyList(props: PropertyListProps) {
|
|
|
25
25
|
|
|
26
26
|
export interface PropertyRowProps {
|
|
27
27
|
label: string;
|
|
28
|
+
// Optional props accept an explicit `undefined` so callers compiled with
|
|
29
|
+
// `exactOptionalPropertyTypes` can forward their own optional props directly.
|
|
28
30
|
/** The value to display. Can be a string, number, or JSX element. */
|
|
29
|
-
value?: string | number | JSX.Element;
|
|
31
|
+
value?: string | number | JSX.Element | undefined;
|
|
30
32
|
/** If true, treat string values as paths and truncate long ones. */
|
|
31
|
-
isPath?: boolean;
|
|
33
|
+
isPath?: boolean | undefined;
|
|
32
34
|
/** Maximum path segments to show (default: 3). */
|
|
33
|
-
maxPathSegments?: number;
|
|
35
|
+
maxPathSegments?: number | undefined;
|
|
34
36
|
/** Called with the full value when a truncated path is clicked. */
|
|
35
|
-
onPathClick?: (fullValue: string) => void;
|
|
36
|
-
valueClass?: string;
|
|
37
|
+
onPathClick?: ((fullValue: string) => void) | undefined;
|
|
38
|
+
valueClass?: string | undefined;
|
|
37
39
|
/** Children can be used instead of value for complex content. */
|
|
38
|
-
children?: JSX.Element;
|
|
40
|
+
children?: JSX.Element | undefined;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
function truncatePath(
|
|
@@ -8,6 +8,8 @@ export interface SearchInputProps {
|
|
|
8
8
|
class?: string;
|
|
9
9
|
'aria-label'?: string;
|
|
10
10
|
onKeyDown?: JSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent>;
|
|
11
|
+
/** Handle on the input itself, for callers that need to focus it. */
|
|
12
|
+
ref?: (element: HTMLInputElement) => void;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -19,6 +21,7 @@ export function SearchInput(props: SearchInputProps) {
|
|
|
19
21
|
<Search class="zylem-search-icon" />
|
|
20
22
|
<input
|
|
21
23
|
type="search"
|
|
24
|
+
ref={props.ref}
|
|
22
25
|
class="zylem-field zylem-search-input"
|
|
23
26
|
value={props.value ?? ''}
|
|
24
27
|
placeholder={props.placeholder ?? 'Search...'}
|
|
@@ -31,7 +31,7 @@ globalStyle('.zylem-select-trigger[data-expanded] .zylem-select-icon', {
|
|
|
31
31
|
});
|
|
32
32
|
|
|
33
33
|
globalStyle('.zylem-select-content', {
|
|
34
|
-
zIndex:
|
|
34
|
+
zIndex: vars.layers.menu,
|
|
35
35
|
borderRadius: vars.radii.control,
|
|
36
36
|
border: '1px solid rgba(97, 166, 232, 0.45)',
|
|
37
37
|
background: vars.material.glassPanelDark,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Select as KSelect } from '@kobalte/core';
|
|
2
2
|
import { Show } from 'solid-js';
|
|
3
|
+
import { createDismissGuard, useLayer } from '../../layers';
|
|
3
4
|
|
|
4
5
|
export interface SelectOption {
|
|
5
6
|
value: string;
|
|
@@ -22,6 +23,8 @@ export interface SelectProps {
|
|
|
22
23
|
* HyperGlass select: inset glass trigger opening a floating glass sheet.
|
|
23
24
|
*/
|
|
24
25
|
export function Select(props: SelectProps) {
|
|
26
|
+
const layer = useLayer('menu');
|
|
27
|
+
const dismiss = createDismissGuard();
|
|
25
28
|
const selected = () =>
|
|
26
29
|
props.options.find((option) => option.value === props.value) ?? null;
|
|
27
30
|
|
|
@@ -50,6 +53,7 @@ export function Select(props: SelectProps) {
|
|
|
50
53
|
<KSelect.Label class="zylem-field-label">{props.label}</KSelect.Label>
|
|
51
54
|
</Show>
|
|
52
55
|
<KSelect.Trigger
|
|
56
|
+
ref={layer.anchorRef}
|
|
53
57
|
class="zylem-field zylem-select-trigger"
|
|
54
58
|
aria-label={props.label}
|
|
55
59
|
>
|
|
@@ -58,11 +62,19 @@ export function Select(props: SelectProps) {
|
|
|
58
62
|
</KSelect.Value>
|
|
59
63
|
<KSelect.Icon class="zylem-select-icon">▼</KSelect.Icon>
|
|
60
64
|
</KSelect.Trigger>
|
|
61
|
-
<
|
|
62
|
-
|
|
63
|
-
<KSelect.
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
<Show when={layer.mount()} keyed>
|
|
66
|
+
{(mount) => (
|
|
67
|
+
<KSelect.Portal mount={mount}>
|
|
68
|
+
<KSelect.Content
|
|
69
|
+
ref={dismiss.ref}
|
|
70
|
+
class="zylem-select-content"
|
|
71
|
+
onInteractOutside={dismiss.onInteractOutside}
|
|
72
|
+
>
|
|
73
|
+
<KSelect.Listbox class="zylem-select-listbox zylem-scroll" />
|
|
74
|
+
</KSelect.Content>
|
|
75
|
+
</KSelect.Portal>
|
|
76
|
+
)}
|
|
77
|
+
</Show>
|
|
66
78
|
</KSelect.Root>
|
|
67
79
|
);
|
|
68
80
|
}
|
|
@@ -38,6 +38,9 @@ globalStyle('.zylem-toolbar-btn:hover', {
|
|
|
38
38
|
globalStyle('.zylem-toolbar-btn:active', {
|
|
39
39
|
transform: 'translateY(1px)',
|
|
40
40
|
filter: 'brightness(0.92)',
|
|
41
|
+
// CodePen-inspired press: compressed drop shadow + bright bottom rim flash.
|
|
42
|
+
boxShadow:
|
|
43
|
+
'inset 0 1px 0 rgba(255,255,255,0.22), inset 0 -1px 0 rgba(255,255,255,0.55), inset 0 2px 3px rgba(0,0,0,0.28), 0 1px 3px rgba(0,0,0,0.28)',
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
globalStyle('.zylem-toolbar-btn:focus-visible', {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Button as KButton, Tooltip as KTooltip } from '@kobalte/core';
|
|
2
|
-
import type
|
|
2
|
+
import { Show, type JSX } from 'solid-js';
|
|
3
|
+
import { useLayer } from '../../layers';
|
|
3
4
|
|
|
4
5
|
export interface ToolbarButtonProps {
|
|
5
6
|
label: string;
|
|
@@ -13,11 +14,16 @@ export interface ToolbarButtonProps {
|
|
|
13
14
|
/**
|
|
14
15
|
* HyperGlass toolbar button: square glass icon button with a tooltip.
|
|
15
16
|
* Selected state renders as a live-green jewel.
|
|
17
|
+
*
|
|
18
|
+
* The tooltip portals into the layer container for its own tree, so it stays
|
|
19
|
+
* styled when the toolbar lives inside a web component's shadow root.
|
|
16
20
|
*/
|
|
17
21
|
export function ToolbarButton(props: ToolbarButtonProps) {
|
|
22
|
+
const layer = useLayer('tooltip');
|
|
23
|
+
|
|
18
24
|
return (
|
|
19
25
|
<KTooltip.Root openDelay={400}>
|
|
20
|
-
<KTooltip.Trigger as="span">
|
|
26
|
+
<KTooltip.Trigger as="span" ref={layer.anchorRef}>
|
|
21
27
|
<KButton.Root
|
|
22
28
|
aria-label={props.label}
|
|
23
29
|
onClick={props.onClick}
|
|
@@ -32,11 +38,15 @@ export function ToolbarButton(props: ToolbarButtonProps) {
|
|
|
32
38
|
{props.children}
|
|
33
39
|
</KButton.Root>
|
|
34
40
|
</KTooltip.Trigger>
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
<Show when={layer.mount()} keyed>
|
|
42
|
+
{(mount) => (
|
|
43
|
+
<KTooltip.Portal mount={mount}>
|
|
44
|
+
<KTooltip.Content class="zylem-tooltip">
|
|
45
|
+
{props.label}
|
|
46
|
+
</KTooltip.Content>
|
|
47
|
+
</KTooltip.Portal>
|
|
48
|
+
)}
|
|
49
|
+
</Show>
|
|
40
50
|
</KTooltip.Root>
|
|
41
51
|
);
|
|
42
52
|
}
|
|
@@ -7,7 +7,8 @@ const enter = keyframes({
|
|
|
7
7
|
});
|
|
8
8
|
|
|
9
9
|
globalStyle('.zylem-tooltip', {
|
|
10
|
-
|
|
10
|
+
position: 'relative',
|
|
11
|
+
zIndex: vars.layers.tooltip,
|
|
11
12
|
maxWidth: '260px',
|
|
12
13
|
padding: `${vars.spacing.xs} ${vars.spacing.sm}`,
|
|
13
14
|
borderRadius: vars.radii.control,
|
|
@@ -19,6 +20,8 @@ globalStyle('.zylem-tooltip', {
|
|
|
19
20
|
color: vars.colors.text,
|
|
20
21
|
fontFamily: vars.typography.fontFamily,
|
|
21
22
|
fontSize: `calc(${vars.typography.fontSize} - 1px)`,
|
|
23
|
+
fontWeight: 600,
|
|
24
|
+
letterSpacing: '0.02em',
|
|
22
25
|
pointerEvents: 'none',
|
|
23
26
|
'@supports': {
|
|
24
27
|
'not (backdrop-filter: blur(1px))': {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Tooltip as KTooltip } from '@kobalte/core';
|
|
2
|
-
import type
|
|
2
|
+
import { Show, type JSX } from 'solid-js';
|
|
3
|
+
import { useLayer } from '../../layers';
|
|
3
4
|
|
|
4
5
|
export interface TooltipProps {
|
|
5
6
|
/** Tooltip body. */
|
|
@@ -11,17 +12,34 @@ export interface TooltipProps {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* HyperGlass tooltip: small floating glass sheet
|
|
15
|
+
* HyperGlass tooltip: small floating glass sheet, portaled above page chrome
|
|
16
|
+
* so it is not trapped by local stacking contexts (grids, panels, etc.).
|
|
17
|
+
*
|
|
18
|
+
* The portal target is resolved from the trigger rather than assumed to be
|
|
19
|
+
* `document.body`, so a tooltip inside a web component's shadow root still
|
|
20
|
+
* picks up the styles injected there.
|
|
15
21
|
*/
|
|
16
22
|
export function Tooltip(props: TooltipProps) {
|
|
23
|
+
const layer = useLayer('tooltip');
|
|
24
|
+
|
|
17
25
|
return (
|
|
18
26
|
<KTooltip.Root placement={props.placement} openDelay={props.openDelay ?? 400}>
|
|
19
|
-
<KTooltip.Trigger
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
<KTooltip.Trigger
|
|
28
|
+
as="span"
|
|
29
|
+
ref={layer.anchorRef}
|
|
30
|
+
style={{ display: 'flex', width: '100%', height: '100%', 'min-width': 0 }}
|
|
31
|
+
>
|
|
32
|
+
{props.children}
|
|
33
|
+
</KTooltip.Trigger>
|
|
34
|
+
<Show when={layer.mount()}>
|
|
35
|
+
{(mount) => (
|
|
36
|
+
<KTooltip.Portal mount={mount()}>
|
|
37
|
+
<KTooltip.Content class="zylem-tooltip">
|
|
38
|
+
{props.content}
|
|
39
|
+
</KTooltip.Content>
|
|
40
|
+
</KTooltip.Portal>
|
|
41
|
+
)}
|
|
42
|
+
</Show>
|
|
25
43
|
</KTooltip.Root>
|
|
26
44
|
);
|
|
27
45
|
}
|
|
@@ -4,14 +4,16 @@ import PanelBottomOpen from 'lucide-solid/icons/panel-bottom-open';
|
|
|
4
4
|
import X from 'lucide-solid/icons/x';
|
|
5
5
|
|
|
6
6
|
export interface WindowControlsProps {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Optional props accept an explicit `undefined` so callers compiled with
|
|
8
|
+
// `exactOptionalPropertyTypes` can forward their own optional props directly.
|
|
9
|
+
onClose?: (() => void) | undefined;
|
|
10
|
+
onCollapse?: (() => void) | undefined;
|
|
9
11
|
/** Show the "expand" collapse icon when the panel is collapsed. */
|
|
10
|
-
collapsed?: boolean;
|
|
12
|
+
collapsed?: boolean | undefined;
|
|
11
13
|
/** Accessible label/title for the close button (default "Close panel"). */
|
|
12
|
-
closeLabel?: string;
|
|
14
|
+
closeLabel?: string | undefined;
|
|
13
15
|
/** Forwarded as data-testid on the close button. */
|
|
14
|
-
closeTestId?: string;
|
|
16
|
+
closeTestId?: string | undefined;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
/**
|
package/src/components/index.ts
CHANGED
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
* vanilla-extract recipes/styles driven by the Zylem token system.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// Layering. Overlays here use it by default; consumers need it only to scope a
|
|
9
|
+
// subtree (a modal) or to layer their own chrome on the same ladder.
|
|
10
|
+
export {
|
|
11
|
+
LayerProvider,
|
|
12
|
+
LayerScope,
|
|
13
|
+
useLayer,
|
|
14
|
+
resolveLayerRoot,
|
|
15
|
+
ensureLayerContainer,
|
|
16
|
+
createDismissGuard,
|
|
17
|
+
LAYER_PORTAL_CLASS,
|
|
18
|
+
type DismissalEvent,
|
|
19
|
+
type DismissGuard,
|
|
20
|
+
type LayerProviderProps,
|
|
21
|
+
type LayerScopeProps,
|
|
22
|
+
type LayerTier,
|
|
23
|
+
type ResolvedLayer,
|
|
24
|
+
} from '../layers';
|
|
25
|
+
|
|
8
26
|
// Primitives
|
|
9
27
|
export { Button, type ButtonProps, type ButtonVariant, type ButtonSize } from './Button/Button';
|
|
10
28
|
export { ToolbarButton, type ToolbarButtonProps } from './ToolbarButton/ToolbarButton';
|
|
@@ -64,6 +82,15 @@ export {
|
|
|
64
82
|
type SidebarItemProps,
|
|
65
83
|
} from './Sidebar/Sidebar';
|
|
66
84
|
export { Card, type CardProps } from './Card/Card';
|
|
85
|
+
export { MenuButton, type MenuButtonProps } from './MenuButton/MenuButton';
|
|
86
|
+
export { ItemPicker, type ItemPickerProps } from './ItemPicker/ItemPicker';
|
|
87
|
+
export {
|
|
88
|
+
filterItems,
|
|
89
|
+
groupItems,
|
|
90
|
+
UNGROUPED_LABEL,
|
|
91
|
+
type PickerItem,
|
|
92
|
+
type PickerGroup,
|
|
93
|
+
} from './ItemPicker/filter-items';
|
|
67
94
|
export { SearchInput, type SearchInputProps } from './SearchInput/SearchInput';
|
|
68
95
|
export { EmptyState, type EmptyStateProps } from './EmptyState/EmptyState';
|
|
69
96
|
export { ScrollArea, type ScrollAreaProps } from './ScrollArea/ScrollArea';
|
|
@@ -74,7 +74,10 @@ globalStyle('.accordion-drag-ghost', {
|
|
|
74
74
|
backdropFilter: `blur(${vars.effects.blurSm})`,
|
|
75
75
|
boxShadow: `0 8px 24px rgba(0, 0, 0, 0.5), ${vars.effects.glowDanger}`,
|
|
76
76
|
position: 'fixed',
|
|
77
|
-
|
|
77
|
+
// Top of the panel tier: the ghost follows the cursor over every panel,
|
|
78
|
+
// including the one it was torn from, but is still page furniture rather than
|
|
79
|
+
// an overlay, so it stays under modals and menus.
|
|
80
|
+
zIndex: `calc(${vars.layers.panel} + 900)`,
|
|
78
81
|
pointerEvents: 'none',
|
|
79
82
|
});
|
|
80
83
|
|
|
@@ -155,42 +155,64 @@ globalStyle('.zylem-section-title', {
|
|
|
155
155
|
textTransform: 'uppercase',
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
/** Entity grid:
|
|
158
|
+
/** Entity grid: glossy glass icon tiles. Type names use portaled `.zylem-tooltip`. */
|
|
159
159
|
globalStyle('.entity-grid', {
|
|
160
160
|
display: 'grid',
|
|
161
161
|
gridTemplateColumns: 'repeat(auto-fill, minmax(40px, 1fr))',
|
|
162
162
|
gap: vars.spacing.xs,
|
|
163
163
|
});
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
/** Tooltip/trigger wrappers must fill the cell so the icon button keeps a square. */
|
|
166
|
+
globalStyle('.entity-grid > *', {
|
|
167
|
+
display: 'flex',
|
|
168
|
+
minWidth: 0,
|
|
166
169
|
aspectRatio: '1',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
globalStyle('.entity-grid-item', {
|
|
173
|
+
position: 'relative',
|
|
174
|
+
flex: 1,
|
|
175
|
+
width: '100%',
|
|
176
|
+
height: '100%',
|
|
167
177
|
display: 'flex',
|
|
168
178
|
alignItems: 'center',
|
|
169
179
|
justifyContent: 'center',
|
|
170
|
-
background: vars.material.
|
|
171
|
-
border: '1px solid rgba(
|
|
180
|
+
background: vars.material.buttonGlass,
|
|
181
|
+
border: '1px solid rgba(150, 210, 255, 0.5)',
|
|
172
182
|
borderRadius: vars.radii.control,
|
|
173
|
-
boxShadow:
|
|
174
|
-
'inset 0 1px 0 rgba(255,255,255,0.1), 0 2px 6px rgba(0,0,0,0.3)',
|
|
183
|
+
boxShadow: vars.effects.shadowButton,
|
|
175
184
|
cursor: 'pointer',
|
|
176
|
-
|
|
185
|
+
color: vars.colors.textSecondary,
|
|
186
|
+
transition: `background ${vars.motion.fast} ${vars.motion.easeOut}, border-color ${vars.motion.fast} ${vars.motion.easeOut}, transform ${vars.motion.fast} ${vars.motion.easeOut}, box-shadow ${vars.motion.fast} ${vars.motion.easeOut}, filter ${vars.motion.fast} ${vars.motion.easeOut}`,
|
|
177
187
|
});
|
|
178
188
|
|
|
179
189
|
globalStyle('.entity-grid-item:hover', {
|
|
180
190
|
background: vars.material.buttonGlassHover,
|
|
181
|
-
borderColor: 'rgba(150, 210, 255, 0.
|
|
182
|
-
boxShadow:
|
|
191
|
+
borderColor: 'rgba(150, 210, 255, 0.75)',
|
|
192
|
+
boxShadow: `${vars.effects.shadowButton}, ${vars.effects.glowPrimary}`,
|
|
183
193
|
transform: 'translateY(-1px)',
|
|
194
|
+
color: '#F4FAFF',
|
|
184
195
|
});
|
|
185
196
|
|
|
186
197
|
globalStyle('.entity-grid-item:active', {
|
|
187
198
|
transform: 'translateY(1px)',
|
|
199
|
+
filter: 'brightness(0.92)',
|
|
200
|
+
boxShadow:
|
|
201
|
+
'inset 0 1px 0 rgba(255,255,255,0.22), inset 0 -1px 0 rgba(255,255,255,0.55), inset 0 2px 3px rgba(0,0,0,0.28), 0 1px 3px rgba(0,0,0,0.28)',
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
globalStyle('.entity-grid-item:focus-visible', {
|
|
205
|
+
outline: 'none',
|
|
206
|
+
boxShadow: `${vars.effects.shadowButton}, ${vars.effects.focusRing}`,
|
|
188
207
|
});
|
|
189
208
|
|
|
190
209
|
globalStyle('.entity-icon', {
|
|
191
210
|
width: '20px',
|
|
192
211
|
height: '20px',
|
|
193
|
-
color:
|
|
212
|
+
color: 'currentColor',
|
|
213
|
+
position: 'relative',
|
|
214
|
+
zIndex: 1,
|
|
215
|
+
filter: 'drop-shadow(0 1px 1px rgba(0,0,0,0.55))',
|
|
194
216
|
});
|
|
195
217
|
|
|
196
218
|
globalStyle('.entity-grid-item:hover .entity-icon', {
|
|
@@ -3,12 +3,14 @@ import { vars } from '../theme.css';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* HyperGlass foundation styles: app root background treatment, visible
|
|
6
|
-
* Aqua-inspired glass scrollbars,
|
|
7
|
-
*
|
|
8
|
-
* page-level
|
|
6
|
+
* Aqua-inspired glass scrollbars, shared focus rings, and a light CodePen-
|
|
7
|
+
* inspired specular sheen on glossy controls. Component-level glass
|
|
8
|
+
* treatments live in each component's recipe; this file covers page-level
|
|
9
|
+
* chrome plus shared lighting primitives.
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
// Root surface: dark navy with a soft radial glow
|
|
12
|
+
// Root surface: dark navy with a soft radial glow, angled light wash, and a
|
|
13
|
+
// faint blueprint grid.
|
|
12
14
|
globalStyle('.zylem-hyperglass-root', {
|
|
13
15
|
minHeight: '100vh',
|
|
14
16
|
color: vars.colors.text,
|
|
@@ -16,10 +18,11 @@ globalStyle('.zylem-hyperglass-root', {
|
|
|
16
18
|
backgroundColor: vars.colors.background,
|
|
17
19
|
backgroundImage: `
|
|
18
20
|
radial-gradient(circle at 30% 0%, rgba(97, 166, 232, 0.18), transparent 32%),
|
|
21
|
+
linear-gradient(-75deg, rgba(255, 255, 255, 0) 0%, rgba(97, 166, 232, 0.07) 42%, rgba(255, 255, 255, 0) 68%),
|
|
19
22
|
linear-gradient(rgba(97, 166, 232, 0.045) 1px, transparent 1px),
|
|
20
23
|
linear-gradient(90deg, rgba(97, 166, 232, 0.045) 1px, transparent 1px)
|
|
21
24
|
`,
|
|
22
|
-
backgroundSize: '100% 100%, 24px 24px, 24px 24px',
|
|
25
|
+
backgroundSize: '100% 100%, 100% 100%, 24px 24px, 24px 24px',
|
|
23
26
|
});
|
|
24
27
|
|
|
25
28
|
// Visible focus ring on any keyboard-focused interactive element.
|
|
@@ -31,6 +34,78 @@ globalStyle(
|
|
|
31
34
|
},
|
|
32
35
|
);
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Specular sheen on glossy interactive hosts — a soft diagonal band that
|
|
39
|
+
* nudges on hover and shifts toward the top on press (CodePen glass lighting,
|
|
40
|
+
* dialed down for HyperGlass). Uses ::before so label/icon content paints above.
|
|
41
|
+
* Applied on the control classes themselves (not under `.zylem-hyperglass-root`)
|
|
42
|
+
* so portaled surfaces like Dialog keep the same look.
|
|
43
|
+
* Pseudo selectors are listed per-host (comma lists don't inherit a trailing ::before).
|
|
44
|
+
*/
|
|
45
|
+
// Absolute-positioned hosts (e.g. `.zylem-console-clear`) already form a
|
|
46
|
+
// containing block for the sheen — don't override them to `relative` or they
|
|
47
|
+
// stretch in flex parents. `.entity-grid-item` already sets `relative` in
|
|
48
|
+
// editor-base.
|
|
49
|
+
globalStyle('.zylem-button:not(.zylem-console-clear), .zylem-toolbar-btn', {
|
|
50
|
+
position: 'relative',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
globalStyle(
|
|
54
|
+
'.zylem-button::before, .zylem-toolbar-btn::before, .entity-grid-item::before',
|
|
55
|
+
{
|
|
56
|
+
content: '""',
|
|
57
|
+
position: 'absolute',
|
|
58
|
+
inset: 0,
|
|
59
|
+
borderRadius: 'inherit',
|
|
60
|
+
pointerEvents: 'none',
|
|
61
|
+
background: `linear-gradient(
|
|
62
|
+
-45deg,
|
|
63
|
+
rgba(255, 255, 255, 0) 0%,
|
|
64
|
+
rgba(255, 255, 255, 0.42) 40%,
|
|
65
|
+
rgba(255, 255, 255, 0.42) 50%,
|
|
66
|
+
rgba(255, 255, 255, 0) 55%
|
|
67
|
+
)`,
|
|
68
|
+
backgroundSize: '200% 200%',
|
|
69
|
+
backgroundPosition: '0% 50%',
|
|
70
|
+
backgroundRepeat: 'no-repeat',
|
|
71
|
+
mixBlendMode: 'screen',
|
|
72
|
+
opacity: 0.4,
|
|
73
|
+
transition: `background-position ${vars.motion.normal} ${vars.motion.easeOut}, opacity ${vars.motion.fast} ${vars.motion.easeOut}`,
|
|
74
|
+
'@media': {
|
|
75
|
+
'(prefers-reduced-motion: reduce)': {
|
|
76
|
+
transition: 'none',
|
|
77
|
+
backgroundPosition: '0% 50%',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
globalStyle(
|
|
84
|
+
'.zylem-button:hover:not([disabled])::before, .zylem-toolbar-btn:hover:not([disabled])::before, .entity-grid-item:hover::before',
|
|
85
|
+
{
|
|
86
|
+
backgroundPosition: '25% 50%',
|
|
87
|
+
opacity: 0.5,
|
|
88
|
+
'@media': {
|
|
89
|
+
'(prefers-reduced-motion: reduce)': {
|
|
90
|
+
backgroundPosition: '0% 50%',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
globalStyle(
|
|
97
|
+
'.zylem-button:active:not([disabled])::before, .zylem-toolbar-btn:active:not([disabled])::before, .entity-grid-item:active::before',
|
|
98
|
+
{
|
|
99
|
+
backgroundPosition: '50% 15%',
|
|
100
|
+
opacity: 0.58,
|
|
101
|
+
'@media': {
|
|
102
|
+
'(prefers-reduced-motion: reduce)': {
|
|
103
|
+
backgroundPosition: '0% 50%',
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
34
109
|
/**
|
|
35
110
|
* Glass scrollbars — apply `.zylem-scroll` (or rely on the root cascade for
|
|
36
111
|
* `.zylem-hyperglass-root`) to get the visible Aqua-style rail.
|