@arnilo/liveframe-starter 0.0.0-stage → 0.3.0-rc.1

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.
@@ -0,0 +1,3 @@
1
+ import { type DesignSystem } from "@arnilo/liveframe";
2
+ /** The starter design system: complete for the starter catalog, with a tone axis and one extension property. */
3
+ export declare const designSystem: DesignSystem;
@@ -0,0 +1,276 @@
1
+ import { validateDesignSystem } from "@arnilo/liveframe";
2
+ import { catalog, valid } from "./catalog.js";
3
+ const px = (value) => ({ value, unit: "px" });
4
+ const shadowLayer = (role, alpha, y, blur, spread, inset = false) => ({
5
+ color: { role, alpha },
6
+ offsetX: px(0),
7
+ offsetY: px(y),
8
+ blur: px(blur),
9
+ spread: px(spread),
10
+ ...(inset ? { inset } : {}),
11
+ });
12
+ const values = {
13
+ "radius.control": { kind: "radius", value: px(6) },
14
+ "radius.plate": { kind: "radius", value: px(8) },
15
+ "radius.pill": { kind: "radius", value: px(999) },
16
+ "border.hairline": { kind: "border-width", value: px(1) },
17
+ "border.focus": { kind: "border-width", value: px(2) },
18
+ "space.0": { kind: "spacing", value: px(0) },
19
+ "space.1": { kind: "spacing", value: px(4) },
20
+ "space.2": { kind: "spacing", value: px(8) },
21
+ "space.3": { kind: "spacing", value: px(12) },
22
+ "space.4": { kind: "spacing", value: px(16) },
23
+ "space.5": { kind: "spacing", value: px(24) },
24
+ "space.6": { kind: "spacing", value: px(32) },
25
+ "space.7": { kind: "spacing", value: px(48) },
26
+ "size.glyph": { kind: "size", value: px(16) },
27
+ "size.panel": { kind: "size", value: px(360) },
28
+ "bp.sm": { kind: "breakpoint", value: px(384) },
29
+ "bp.md": { kind: "breakpoint", value: px(720) },
30
+ "motion.fast": { kind: "motion-duration", value: { value: 120, unit: "ms" } },
31
+ "ease-out": { kind: "easing", value: [0.2, 0, 0, 1] },
32
+ "opacity.dim": { kind: "opacity", value: 0.5 },
33
+ "shadow.pop": {
34
+ kind: "shadow",
35
+ value: [shadowLayer("shadow.ambient", 0.14, 2, 6, 0), shadowLayer("shadow.ambient", 0.3, 16, 32, -12)],
36
+ },
37
+ };
38
+ const motion = { transitionDuration: "motion.fast", transitionTiming: "ease-out" };
39
+ const focus = {
40
+ outlineColor: "focus.ring",
41
+ outlineWidth: "border.focus",
42
+ outlineOffset: px(2),
43
+ outlineStyle: "solid",
44
+ };
45
+ const control = {
46
+ backgroundColor: "surface.raised",
47
+ textColor: "accent.text",
48
+ borderColor: "accent.edge",
49
+ borderWidth: "border.hairline",
50
+ borderRadius: "radius.control",
51
+ paddingX: "space.4",
52
+ paddingY: "space.0",
53
+ gap: "space.2",
54
+ typography: "label",
55
+ ...motion,
56
+ };
57
+ const field = {
58
+ backgroundColor: "surface.raised",
59
+ textColor: "text.primary",
60
+ placeholderColor: "text.muted",
61
+ borderColor: "border.strong",
62
+ borderWidth: "border.hairline",
63
+ borderRadius: "radius.control",
64
+ paddingX: "space.3",
65
+ paddingY: "space.2",
66
+ typography: "body",
67
+ ...motion,
68
+ };
69
+ const floating = {
70
+ backgroundColor: "surface.raised",
71
+ textColor: "text.primary",
72
+ borderColor: "border.strong",
73
+ borderWidth: "border.hairline",
74
+ borderRadius: "radius.plate",
75
+ padding: "space.1",
76
+ typography: "body",
77
+ shadow: "shadow.pop",
78
+ };
79
+ const item = {
80
+ backgroundColor: "none",
81
+ textColor: "text.primary",
82
+ borderWidth: px(0),
83
+ borderRadius: "radius.control",
84
+ paddingX: "space.3",
85
+ paddingY: "space.2",
86
+ gap: "space.2",
87
+ typography: "label",
88
+ };
89
+ const mark = {
90
+ backgroundColor: "surface.raised",
91
+ borderColor: "border.strong",
92
+ borderWidth: "border.hairline",
93
+ borderRadius: "radius.control",
94
+ };
95
+ const selectedMark = {
96
+ backgroundColor: "accent.fill",
97
+ textColor: "text.on-accent",
98
+ borderColor: "accent.fill",
99
+ };
100
+ const disabledField = {
101
+ textColor: "text.disabled",
102
+ backgroundColor: "surface.well",
103
+ borderColor: "border.subtle",
104
+ };
105
+ const disabledControl = { ...disabledField, backgroundColor: "surface.well" };
106
+ const recipes = {};
107
+ function set(prefix, states) {
108
+ for (const [state, recipe] of Object.entries(states))
109
+ recipes[`${prefix}.${state}`] = recipe;
110
+ }
111
+ const empty = {};
112
+ set("box.*.root", { rest: empty });
113
+ set("box.*.rail", { rest: empty });
114
+ set("box.plate.root", {
115
+ rest: {
116
+ backgroundColor: "surface.raised",
117
+ borderColor: "border.subtle",
118
+ borderWidth: "border.hairline",
119
+ borderRadius: "radius.plate",
120
+ padding: "space.4",
121
+ gap: "space.3",
122
+ },
123
+ });
124
+ set("box.rule.root", {
125
+ rest: { borderColor: "border.subtle", borderTopWidth: "border.hairline", paddingY: "space.3", gap: "space.1" },
126
+ });
127
+ set("box.callout.root", { rest: { gap: "space.3", paddingY: "space.1" } });
128
+ set("box.callout.rail", { rest: { backgroundColor: "accent.edge", borderRadius: "radius.pill" } });
129
+ for (const kind of ["stack", "inline", "grid", "icon", "image"])
130
+ set(`${kind}.*.root`, { rest: empty });
131
+ set("scroll.*.root", { rest: empty });
132
+ set("scroll.*.scrollbar", { rest: empty });
133
+ set("scroll.*.viewport", { rest: { scrollbarThumbColor: "surface.scrollbar", scrollbarTrackColor: "none" } });
134
+ set("separator.*.root", { rest: { borderColor: "border.subtle", borderTopWidth: "border.hairline" } });
135
+ set("text.*.root", { rest: { textColor: "text.primary", typography: "body" } });
136
+ set("hint.*.root", { rest: { textColor: "text.muted", typography: "caption" } });
137
+ set("heading.*.root", { rest: { textColor: "text.primary", typography: "heading.2" } });
138
+ set("link.*.root", {
139
+ rest: { textColor: "accent.text", typography: "body" },
140
+ hover: { textColor: "accent.fill.hover" },
141
+ focus: { ...focus, borderRadius: "radius.control" },
142
+ });
143
+ set("link.*.label", { rest: empty });
144
+ set("button.*.root", {
145
+ rest: control,
146
+ hover: { backgroundColor: "surface.hover" },
147
+ active: { translateY: px(1) },
148
+ focus,
149
+ disabled: disabledControl,
150
+ });
151
+ set("button.primary.root", {
152
+ rest: {
153
+ backgroundColor: "accent.fill",
154
+ textColor: "text.on-accent",
155
+ borderColor: "none",
156
+ extensions: { sheen: "shadow.highlight" },
157
+ },
158
+ hover: { backgroundColor: "accent.fill.hover" },
159
+ // The page's ink: the one ring colour that clears 3:1 against the page and against the fill (the audit measures both).
160
+ focus: { ...focus, outlineColor: "text.primary" },
161
+ });
162
+ set("button.ghost.root", {
163
+ rest: { backgroundColor: "none", borderColor: "none", paddingX: "space.2" },
164
+ });
165
+ for (const slot of ["label", "icon"])
166
+ set(`button.*.${slot}`, { rest: empty });
167
+ // A critical button reads the same accent roles through a different family (spec §6.2, role aliases).
168
+ set("button.tone:critical.root", { rest: { roles: { accent: "status.critical" } } });
169
+ set("textField.*.root", { rest: { gap: "space.1" } });
170
+ set("textField.*.label", { rest: { textColor: "text.primary", typography: "label" } });
171
+ set("textField.*.input", {
172
+ rest: field,
173
+ hover: { borderColor: "text.primary" },
174
+ focus: { ...focus, outlineOffset: px(1), borderColor: "focus.ring" },
175
+ disabled: disabledField,
176
+ });
177
+ set("select.*.root", { rest: { gap: "space.1" } });
178
+ set("select.*.label", { rest: { textColor: "text.primary", typography: "label" } });
179
+ set("select.*.trigger", {
180
+ rest: field,
181
+ hover: { borderColor: "text.primary" },
182
+ focus: { ...focus, outlineOffset: px(1), borderColor: "focus.ring" },
183
+ disabled: disabledField,
184
+ });
185
+ set("select.*.list", { rest: floating });
186
+ set("select.*.option", {
187
+ rest: item,
188
+ hover: { backgroundColor: "surface.hover" },
189
+ focus,
190
+ selected: { backgroundColor: "surface.selected" },
191
+ });
192
+ set("checkbox.*.root", {
193
+ rest: { textColor: "text.primary", typography: "label", gap: "space.2" },
194
+ focus,
195
+ disabled: { textColor: "text.disabled" },
196
+ });
197
+ set("checkbox.*.label", { rest: empty });
198
+ set("checkbox.*.indicator", { rest: mark, selected: selectedMark, focus });
199
+ set("switch.*.root", {
200
+ rest: { textColor: "text.primary", typography: "label", gap: "space.3" },
201
+ focus,
202
+ disabled: { textColor: "text.disabled" },
203
+ });
204
+ set("switch.*.label", { rest: empty });
205
+ set("switch.*.track", { rest: { ...mark, borderRadius: "radius.pill" }, selected: selectedMark, focus });
206
+ set("switch.*.thumb", {
207
+ rest: { backgroundColor: "text.primary", borderRadius: "radius.pill" },
208
+ selected: { backgroundColor: "text.on-accent" },
209
+ });
210
+ set("menu.*.root", { rest: empty });
211
+ set("menu.*.trigger", {
212
+ rest: { ...control, textColor: "text.primary", borderColor: "border.strong" },
213
+ hover: { backgroundColor: "surface.hover" },
214
+ focus,
215
+ disabled: disabledControl,
216
+ });
217
+ set("menu.*.content", { rest: floating });
218
+ set("menuItem.*.root", {
219
+ rest: item,
220
+ hover: { backgroundColor: "surface.hover" },
221
+ focus,
222
+ disabled: { textColor: "text.disabled" },
223
+ });
224
+ for (const slot of ["label", "icon"])
225
+ set(`menuItem.*.${slot}`, { rest: empty });
226
+ set("popover.*.root", { rest: empty });
227
+ set("popover.*.trigger", {
228
+ rest: { ...control, textColor: "text.primary", borderColor: "border.strong" },
229
+ hover: { backgroundColor: "surface.hover" },
230
+ focus,
231
+ });
232
+ set("popover.*.content", { rest: floating });
233
+ set("dialog.*.root", { rest: empty });
234
+ set("dialog.*.backdrop", { rest: { backgroundColor: "surface.scrim", backgroundOpacity: 0.5 } });
235
+ set("dialog.*.content", { rest: { ...floating, padding: "space.5", gap: "space.4" } });
236
+ set("dialog.*.title", { rest: { textColor: "text.primary", typography: "heading.3" } });
237
+ set("dialog.*.close", {
238
+ rest: { ...control, textColor: "text.primary", borderColor: "border.strong" },
239
+ hover: { backgroundColor: "surface.hover" },
240
+ focus,
241
+ });
242
+ set("tooltip.*.root", { rest: empty });
243
+ set("tooltip.*.trigger", { rest: empty });
244
+ set("tooltip.*.content", { rest: { ...floating, typography: "caption" } });
245
+ set("tabs.*.root", { rest: empty });
246
+ set("tabs.*.list", { rest: { gap: "space.1" } });
247
+ // A panel with no focusable content is itself a tab stop, so the kind's focus overlay belongs to it.
248
+ set("tabs.*.panel", { rest: { paddingY: "space.3" }, focus });
249
+ set("tab.*.root", {
250
+ rest: {
251
+ backgroundColor: "none",
252
+ textColor: "text.muted",
253
+ borderRadius: "radius.control",
254
+ paddingX: "space.3",
255
+ paddingY: "space.2",
256
+ gap: "space.2",
257
+ typography: "label",
258
+ ...motion,
259
+ },
260
+ hover: { textColor: "text.primary", backgroundColor: "surface.hover" },
261
+ focus,
262
+ disabled: { textColor: "text.disabled" },
263
+ selected: { textColor: "accent.text", shadow: [shadowLayer("accent.fill", 1, -2, 0, 0, true)] },
264
+ });
265
+ set("tab.*.label", { rest: empty });
266
+ set("tab.*.count", { rest: { typography: "mono" } });
267
+ set("tab.*.panel", { rest: empty });
268
+ set("disclosure.*.root", { rest: { textColor: "text.muted", typography: "label", gap: "space.1" } });
269
+ set("disclosure.*.summary", {
270
+ rest: { textColor: "text.muted", typography: "label", paddingY: "space.1", borderRadius: "radius.control" },
271
+ hover: { textColor: "text.primary" },
272
+ focus,
273
+ });
274
+ set("disclosure.*.panel", { rest: { textColor: "text.primary", typography: "body", paddingY: "space.1" } });
275
+ /** The starter design system: complete for the starter catalog, with a tone axis and one extension property. */
276
+ export const designSystem = valid("design system", validateDesignSystem({ format: "liveframe-design-system/1", id: "starter", version: "1.0.0", values, recipes }, { catalog, registry: {} }));
@@ -0,0 +1,3 @@
1
+ import { type Implementation } from "@arnilo/liveframe/react";
2
+ /** One React implementation for every primitive of the starter catalog, built on React Aria Components. */
3
+ export declare const implementations: Implementation[];
@@ -0,0 +1,153 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { implement } from "@arnilo/liveframe/react";
3
+ import { createElement, useState } from "react";
4
+ import * as Aria from "react-aria-components";
5
+ import { Box, Button, Checkbox, Dialog, Disclosure, Grid, Heading, Hint, Icon, Image, Inline, Link, Menu, MenuItem, Popover, Scroll, Select, Separator, Stack, Switch, Tab, Tabs, Text, TextField, Tooltip, } from "./ui.definitions.gen.js";
6
+ // A host-owned value follows the incoming one when the widget sends a new one, and is local between renders.
7
+ function useValue(incoming) {
8
+ const [value, setValue] = useState(incoming);
9
+ const [previous, setPrevious] = useState(incoming);
10
+ if (previous !== incoming) {
11
+ setPrevious(incoming);
12
+ setValue(incoming);
13
+ }
14
+ return [value, setValue];
15
+ }
16
+ const tags = {
17
+ section: "section",
18
+ article: "article",
19
+ aside: "aside",
20
+ header: "header",
21
+ footer: "footer",
22
+ nav: "nav",
23
+ list: "ul",
24
+ listItem: "li",
25
+ terms: "dl",
26
+ term: "dt",
27
+ detail: "dd",
28
+ form: "form",
29
+ group: "div",
30
+ figure: "figure",
31
+ status: "div",
32
+ alert: "div",
33
+ };
34
+ const roles = { group: "group", status: "status", alert: "alert" };
35
+ // `semantics` picks the element; without one a layout primitive stays a plain div.
36
+ function layout(props, className, style, children) {
37
+ const { semantics, label } = props;
38
+ return createElement(semantics ? tags[semantics] : "div", {
39
+ className,
40
+ style,
41
+ ...(semantics && semantics in roles ? { role: roles[semantics] } : {}),
42
+ ...(label ? { "aria-label": label } : {}),
43
+ ...(semantics === "form" ? { onSubmit: (event) => event.preventDefault() } : {}),
44
+ }, children);
45
+ }
46
+ // A small generic glyph set. Replace it with your icon registry: the catalog only says an icon has a name.
47
+ const glyphs = {
48
+ check: "✓",
49
+ close: "×",
50
+ plus: "+",
51
+ minus: "−",
52
+ alert: "!",
53
+ info: "i",
54
+ search: "⌕",
55
+ "chevron-down": "⌄",
56
+ "chevron-right": "›",
57
+ "arrow-right": "→",
58
+ refresh: "↻",
59
+ };
60
+ const glyph = (name) => glyphs[name] ?? "◇";
61
+ const icon = (name, className) => name ? (_jsx("span", { "aria-hidden": "true", className: className, "data-icon": name, children: glyph(name) })) : null;
62
+ /** One React implementation for every primitive of the starter catalog, built on React Aria Components. */
63
+ export const implementations = [
64
+ implement(Box, ({ props, slot, style, children }) => layout(props, slot("root"), style, _jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: slot("rail") }), children] }))),
65
+ implement(Stack, ({ props, slot, style, children }) => layout(props, slot("root"), style, children)),
66
+ implement(Inline, ({ props, slot, style, children }) => layout(props, slot("root"), style, children)),
67
+ implement(Grid, ({ props, slot, style, children }) => layout(props, slot("root"), style, children)),
68
+ implement(Scroll, ({ props, slot, style, children }) => (_jsx("div", { className: slot("root"), style: style, children: _jsx("section", { "aria-label": props.label,
69
+ // biome-ignore lint/a11y/noNoninteractiveTabindex: Keyboard users must be able to focus and scroll this named region.
70
+ tabIndex: 0, className: slot("viewport"), style: {
71
+ overflowX: props.axis === "vertical" ? "hidden" : "auto",
72
+ overflowY: props.axis === "horizontal" ? "hidden" : "auto",
73
+ }, children: children }) }))),
74
+ implement(Separator, ({ props, slot, style }) => (_jsx(Aria.Separator, { orientation: props.orientation ?? "horizontal", className: slot("root"), style: style }))),
75
+ implement(Text, ({ props, slot, style, domId }) => (_jsx("span", { id: domId, className: slot("root"), style: style, children: props.text }))),
76
+ implement(Hint, ({ props, slot, style, domId }) => (_jsx("p", { id: domId, className: slot("root"), style: style, children: props.text }))),
77
+ implement(Heading, ({ props, slot, style, domId }) => createElement(`h${props.level}`, { id: domId, className: slot("root"), style }, props.text)),
78
+ implement(Icon, ({ props, slot, style }) => (_jsx("span", { role: "img", "aria-label": props.label, "aria-hidden": !props.label || undefined, className: slot("root"), style: style, "data-icon": props.name, children: glyph(props.name) }))),
79
+ implement(Image, ({ props, slot, style }) => (_jsx("img", { src: props.src, alt: props.alt, className: slot("root"), style: style }))),
80
+ implement(Link, ({ props, slot, style }) => (_jsx(Aria.Link, { href: props.href, className: `${slot("root")} starter-link`, style: style, children: _jsx("span", { className: slot("label"), children: props.label }) }))),
81
+ implement(Button, ({ props, slot, style, emit }) => (_jsxs(Aria.Button, { isDisabled: props.disabled ?? false, ...(props.current ? { "aria-current": "true" } : {}), ...(props.pressed === undefined ? {} : { "aria-pressed": props.pressed }),
82
+ // A class that does not move with the variant or tone: the primitive CSS reads the `sheen` extension here.
83
+ className: `${slot("root")} starter-button`, style: style, onPress: () => emit("onPress"), children: [icon(props.icon, slot("icon")), _jsx("span", { className: slot("label"), children: props.label })] }))),
84
+ implement(TextField, function TextFieldImplementation({ props, slot, style, emit, domId }) {
85
+ const [value, setValue] = useValue(props.value ?? "");
86
+ const input = {
87
+ id: domId,
88
+ className: slot("input"),
89
+ ...(props.placeholder === undefined ? {} : { placeholder: props.placeholder }),
90
+ onBlur: () => emit("onChange", value),
91
+ onKeyDown: (event) => {
92
+ if (event.key === "Enter" &&
93
+ !event.nativeEvent.isComposing &&
94
+ (!props.multiline || event.ctrlKey || event.metaKey)) {
95
+ emit("onChange", value);
96
+ emit("onCommit", value);
97
+ }
98
+ },
99
+ };
100
+ return (_jsxs(Aria.TextField, { className: slot("root"), style: style, value: value,
101
+ // Bounds count code points, as the validator does, not UTF-16 units.
102
+ onChange: (next) => {
103
+ if (props.maxLength === undefined || Array.from(next).length <= props.maxLength)
104
+ setValue(next);
105
+ }, isDisabled: props.disabled ?? false, isReadOnly: props.readOnly ?? false, ...(props.describedBy === undefined ? {} : { "aria-describedby": props.describedBy }), children: [_jsx(Aria.Label, { className: slot("label"), children: props.label }), props.multiline ? _jsx(Aria.TextArea, { ...input }) : _jsx(Aria.Input, { ...input })] }));
106
+ }),
107
+ implement(Select, function SelectImplementation({ props, slot, style, emit, portal }) {
108
+ const [value, setValue] = useValue(props.value ?? null);
109
+ return (_jsxs(Aria.Select, { "aria-label": props.label, className: slot("root"), style: style, selectedKey: value, isDisabled: props.disabled ?? false, onSelectionChange: (key) => {
110
+ const next = key === null ? null : String(key);
111
+ setValue(next);
112
+ if (next !== null)
113
+ emit("onChange", next);
114
+ }, children: [_jsx(Aria.Label, { className: slot("label"), children: props.label }), _jsx(Aria.Button, { className: slot("trigger"), children: _jsx(Aria.SelectValue, {}) }), _jsx(Aria.Popover, { ...(portal ? { UNSTABLE_portalContainer: portal } : {}), children: _jsx(Aria.ListBox, { className: slot("list"), items: props.options, children: (option) => (_jsx(Aria.ListBoxItem, { id: option.value, textValue: option.label, isDisabled: option.disabled ?? false, className: slot("option"), children: option.label })) }) })] }));
115
+ }),
116
+ implement(Checkbox, function CheckboxImplementation({ props, slot, style, emit }) {
117
+ const [value, setValue] = useValue(props.checked ?? false);
118
+ return (_jsx(Aria.Checkbox, { className: slot("root"), style: style, isSelected: value, isDisabled: props.disabled ?? false, onChange: (checked) => {
119
+ setValue(checked);
120
+ emit("onChange", checked);
121
+ }, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx("span", { "aria-hidden": "true", className: slot("indicator"), children: isSelected ? glyph("check") : "" }), _jsx("span", { className: slot("label"), children: props.label })] })) }));
122
+ }),
123
+ implement(Switch, function SwitchImplementation({ props, slot, style, emit }) {
124
+ const [value, setValue] = useValue(props.checked ?? false);
125
+ return (_jsxs(Aria.Switch, { className: slot("root"), style: style, isSelected: value, isDisabled: props.disabled ?? false, onChange: (checked) => {
126
+ setValue(checked);
127
+ emit("onChange", checked);
128
+ }, children: [_jsx("span", { "aria-hidden": "true", className: slot("track"), children: _jsx("span", { className: slot("thumb") }) }), _jsx("span", { className: slot("label"), children: props.label })] }));
129
+ }),
130
+ implement(Menu, ({ props, slot, style, items, portal }) => (_jsx("div", { className: slot("root"), style: style, children: _jsxs(Aria.MenuTrigger, { children: [_jsx(Aria.Button, { className: slot("trigger"), isDisabled: props.disabled ?? false, children: props.label }), _jsx(Aria.Popover, { ...(portal ? { UNSTABLE_portalContainer: portal } : {}), className: slot("content"), "data-open": "", children: _jsx(Aria.Menu, { "aria-label": props.label, items: items, children: (node) => (_jsxs(Aria.MenuItem, { id: node.id, textValue: String(node.props.label), isDisabled: node.props.disabled === true, className: node.slot("root"), style: node.style, onAction: () => node.emit("onPress"), children: [icon(typeof node.props.icon === "string" ? node.props.icon : undefined, node.slot("icon")), _jsx("span", { className: node.slot("label"), children: String(node.props.label) })] })) }) })] }) }))),
131
+ // Drawn by its `menu`, which reads the item's props, slots and events; this binding satisfies the registry.
132
+ implement(MenuItem, ({ props, slot, style, emit }) => (_jsxs(Aria.MenuItem, { textValue: props.label, className: slot("root"), style: style, isDisabled: props.disabled ?? false, onAction: () => emit("onPress"), children: [icon(props.icon, slot("icon")), _jsx("span", { className: slot("label"), children: props.label })] }))),
133
+ implement(Popover, ({ props, slot, style, children, portal }) => (_jsx("div", { className: slot("root"), style: style, children: _jsxs(Aria.DialogTrigger, { children: [_jsx(Aria.Button, { className: slot("trigger"), children: props.label }), _jsx(Aria.Popover, { ...(portal ? { UNSTABLE_portalContainer: portal } : {}), className: slot("content"), children: _jsx(Aria.Dialog, { "aria-label": props.label, children: children }) })] }) }))),
134
+ implement(Dialog, function DialogImplementation({ props, slot, style, children, emit, portal }) {
135
+ const [open, setOpen] = useValue(props.open ?? false);
136
+ return (_jsx("div", { className: slot("root"), style: style, children: _jsx(Aria.ModalOverlay, { ...(portal ? { UNSTABLE_portalContainer: portal } : {}), isOpen: open && portal !== undefined, isDismissable: true, onOpenChange: (next) => {
137
+ setOpen(next);
138
+ if (!next)
139
+ emit("onClose");
140
+ }, className: slot("backdrop"), children: _jsx(Aria.Modal, { children: _jsxs(Aria.Dialog, { className: slot("content"), "aria-label": props.label, children: [_jsx(Aria.Heading, { slot: "title", className: slot("title"), children: props.label }), children, _jsx(Aria.Button, { slot: "close", className: slot("close"), children: "Close" })] }) }) }) }));
141
+ }),
142
+ implement(Tooltip, ({ props, slot, style, children, portal }) => (_jsx("span", { className: slot("root"), style: style, children: _jsxs(Aria.TooltipTrigger, { children: [_jsx(Aria.Focusable, { children: _jsx("span", { tabIndex: 0, className: slot("trigger"), children: children }) }), _jsx(Aria.Tooltip, { ...(portal ? { UNSTABLE_portalContainer: portal } : {}), className: slot("content"), children: props.text })] }) }))),
143
+ implement(Tabs, function TabsImplementation({ props, slot, style, items, emit }) {
144
+ const [value, setValue] = useValue(props.value ?? String(items[0]?.props.value));
145
+ return (_jsxs(Aria.Tabs, { className: slot("root"), style: style, selectedKey: value, onSelectionChange: (key) => {
146
+ setValue(String(key));
147
+ emit("onChange", String(key));
148
+ }, children: [_jsx(Aria.TabList, { "aria-label": props.label, className: slot("list"), items: items, children: (node) => (_jsxs(Aria.Tab, { id: String(node.props.value), isDisabled: node.props.disabled === true, className: node.slot("root"), style: node.style, children: [_jsx("span", { className: node.slot("label"), children: String(node.props.label) }), typeof node.props.count === "string" ? (_jsx("span", { className: node.slot("count"), children: node.props.count })) : null] })) }), items.map((node) => (_jsx(Aria.TabPanel, { id: String(node.props.value), className: `${slot("panel")} ${node.slot("panel")}`, children: node.children }, node.id)))] }));
149
+ }),
150
+ implement(Disclosure, ({ props, slot, style, children, emit }) => (_jsxs("details", { ...(props.open === undefined ? {} : { open: props.open }), className: slot("root"), style: style, onToggle: (event) => emit("onToggle", event.currentTarget.open), children: [_jsx("summary", { className: slot("summary"), children: props.label }), _jsx("div", { className: slot("panel"), children: children })] }))),
151
+ // Drawn by its `tabs`, like `menuItem`.
152
+ implement(Tab, ({ props, slot, style }) => (_jsxs(Aria.Tab, { id: props.value, isDisabled: props.disabled ?? false, className: slot("root"), style: style, children: [_jsx("span", { className: slot("label"), children: props.label }), props.count === undefined ? null : _jsx("span", { className: slot("count"), children: props.count })] }))),
153
+ ];
@@ -0,0 +1,5 @@
1
+ export { catalog } from "./catalog.js";
2
+ export { designSystem } from "./design-system.js";
3
+ export { implementations } from "./implementations.js";
4
+ export { primitiveCss, styles } from "./styles.js";
5
+ export { themes } from "./themes.js";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { catalog } from "./catalog.js";
2
+ export { designSystem } from "./design-system.js";
3
+ export { implementations } from "./implementations.js";
4
+ export { primitiveCss, styles } from "./styles.js";
5
+ export { themes } from "./themes.js";
@@ -0,0 +1,12 @@
1
+ export declare const primitiveCss = "\n.lf_stack_default_root{display:flex;flex-direction:column}\n.lf_inline_default_root{display:flex;flex-direction:row;align-items:center}\n.lf_grid_default_root{display:grid}\n.lf_scroll_default_viewport{overflow:auto;max-height:100%}\n.starter-button{display:inline-flex;align-items:center;justify-content:center;min-height:2.25rem;cursor:pointer;box-shadow:inset 0 1px 0 var(--lf-p-sheen,transparent)}\n.starter-button[data-disabled]{cursor:default}\n.lf_heading_default_root,.lf_dialog_default_title{margin:0;text-wrap:balance}\n.lf_textField_default_root,.lf_select_default_root{display:flex;flex-direction:column}\n.lf_select_default_trigger{display:flex;justify-content:space-between;align-items:center;min-height:2.25rem;text-align:left}\n.lf_checkbox_default_root,.lf_switch_default_root{display:inline-flex;align-items:center}\n.lf_checkbox_default_indicator{display:inline-flex;align-items:center;justify-content:center;width:1.1em;height:1.1em;line-height:1}\n.lf_switch_default_track{display:inline-flex;align-items:center;width:2.2em;height:1.2em;padding:2px;box-sizing:border-box}\n.lf_switch_default_thumb{width:calc(1.2em - 6px);height:calc(1.2em - 6px)}\n[data-selected] .lf_switch_default_thumb{transform:translateX(1em)}\n.lf_menu_default_trigger,.lf_popover_default_trigger,.lf_dialog_default_close{display:inline-flex;align-items:center;min-height:2.25rem;cursor:pointer}\n.lf_menuItem_default_root,.lf_select_default_option{display:flex;align-items:center;cursor:default;outline:none}\n.lf_dialog_default_backdrop{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}\n.lf_dialog_default_content{max-width:90vw;max-height:90vh;overflow:auto}\n.lf_icon_default_root{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1rem;height:1rem}\n.lf_tabs_default_list{display:flex;flex-wrap:wrap;align-items:center}\n.lf_tab_default_root{display:inline-flex;align-items:center;min-height:2.25rem;cursor:pointer}\n.lf_tooltip_default_trigger{display:inline-flex}\n.lf_image_default_root{max-width:100%}\n.starter-link{text-decoration:underline;text-underline-offset:0.15em}\n.lf_box_plate_root{display:grid;align-content:start;min-width:0}\n.lf_box_callout_root{display:grid;grid-template-columns:3px minmax(0,1fr)}\n.lf_box_default_rail,.lf_box_plate_rail,.lf_box_rule_rail{display:none}\n.lf_box_callout_rail{display:block;width:3px}\n.lf_disclosure_default_summary{width:fit-content;min-height:2rem;cursor:pointer}\n";
2
+ /**
3
+ * The sheets a page loads, in order: base, primitive structure, then one theme sheet and one design-system sheet.
4
+ * Each theme and design system scopes itself to the surface that names it, so all three themes load at once and a
5
+ * switch is an attribute change.
6
+ */
7
+ export declare function styles(): {
8
+ readonly base: string;
9
+ readonly primitives: string;
10
+ readonly themes: string;
11
+ readonly designSystem: string;
12
+ };
package/dist/styles.js ADDED
@@ -0,0 +1,50 @@
1
+ import { baseCss, designSystemCss, themeCss } from "@arnilo/liveframe/css";
2
+ import { catalog, valid } from "./catalog.js";
3
+ import { designSystem } from "./design-system.js";
4
+ import { themes } from "./themes.js";
5
+ // Trusted app CSS owns primitive structure; the design system's recipes own the look (spec §6.2). It names no colour:
6
+ // the one paint it reads is the `sheen` extension, with a transparent fallback.
7
+ export const primitiveCss = `
8
+ .lf_stack_default_root{display:flex;flex-direction:column}
9
+ .lf_inline_default_root{display:flex;flex-direction:row;align-items:center}
10
+ .lf_grid_default_root{display:grid}
11
+ .lf_scroll_default_viewport{overflow:auto;max-height:100%}
12
+ .starter-button{display:inline-flex;align-items:center;justify-content:center;min-height:2.25rem;cursor:pointer;box-shadow:inset 0 1px 0 var(--lf-p-sheen,transparent)}
13
+ .starter-button[data-disabled]{cursor:default}
14
+ .lf_heading_default_root,.lf_dialog_default_title{margin:0;text-wrap:balance}
15
+ .lf_textField_default_root,.lf_select_default_root{display:flex;flex-direction:column}
16
+ .lf_select_default_trigger{display:flex;justify-content:space-between;align-items:center;min-height:2.25rem;text-align:left}
17
+ .lf_checkbox_default_root,.lf_switch_default_root{display:inline-flex;align-items:center}
18
+ .lf_checkbox_default_indicator{display:inline-flex;align-items:center;justify-content:center;width:1.1em;height:1.1em;line-height:1}
19
+ .lf_switch_default_track{display:inline-flex;align-items:center;width:2.2em;height:1.2em;padding:2px;box-sizing:border-box}
20
+ .lf_switch_default_thumb{width:calc(1.2em - 6px);height:calc(1.2em - 6px)}
21
+ [data-selected] .lf_switch_default_thumb{transform:translateX(1em)}
22
+ .lf_menu_default_trigger,.lf_popover_default_trigger,.lf_dialog_default_close{display:inline-flex;align-items:center;min-height:2.25rem;cursor:pointer}
23
+ .lf_menuItem_default_root,.lf_select_default_option{display:flex;align-items:center;cursor:default;outline:none}
24
+ .lf_dialog_default_backdrop{position:fixed;inset:0;display:flex;align-items:center;justify-content:center}
25
+ .lf_dialog_default_content{max-width:90vw;max-height:90vh;overflow:auto}
26
+ .lf_icon_default_root{display:inline-flex;align-items:center;justify-content:center;flex:none;width:1rem;height:1rem}
27
+ .lf_tabs_default_list{display:flex;flex-wrap:wrap;align-items:center}
28
+ .lf_tab_default_root{display:inline-flex;align-items:center;min-height:2.25rem;cursor:pointer}
29
+ .lf_tooltip_default_trigger{display:inline-flex}
30
+ .lf_image_default_root{max-width:100%}
31
+ .starter-link{text-decoration:underline;text-underline-offset:0.15em}
32
+ .lf_box_plate_root{display:grid;align-content:start;min-width:0}
33
+ .lf_box_callout_root{display:grid;grid-template-columns:3px minmax(0,1fr)}
34
+ .lf_box_default_rail,.lf_box_plate_rail,.lf_box_rule_rail{display:none}
35
+ .lf_box_callout_rail{display:block;width:3px}
36
+ .lf_disclosure_default_summary{width:fit-content;min-height:2rem;cursor:pointer}
37
+ `;
38
+ /**
39
+ * The sheets a page loads, in order: base, primitive structure, then one theme sheet and one design-system sheet.
40
+ * Each theme and design system scopes itself to the surface that names it, so all three themes load at once and a
41
+ * switch is an attribute change.
42
+ */
43
+ export function styles() {
44
+ return {
45
+ base: valid("base sheet", baseCss(catalog)),
46
+ primitives: primitiveCss,
47
+ themes: themes.map((theme) => valid(`${theme.id} sheet`, themeCss(theme, catalog))).join("\n"),
48
+ designSystem: valid("design-system sheet", designSystemCss(designSystem, { catalog, registry: {} })),
49
+ };
50
+ }
@@ -0,0 +1,3 @@
1
+ import { type Theme } from "@arnilo/liveframe";
2
+ /** The light, dark and high-contrast themes, validated against the starter vocabulary. */
3
+ export declare const themes: [Theme, ...Theme[]];