@surflane/ui 0.0.0-stage → 0.2.0

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.
Files changed (89) hide show
  1. package/LICENSE-APACHE +202 -0
  2. package/LICENSE-MIT +18 -0
  3. package/README.md +55 -2
  4. package/dist/advanced-forms.d.ts +115 -0
  5. package/dist/advanced-forms.js +93 -0
  6. package/dist/app-shell.d.ts +23 -0
  7. package/dist/app-shell.js +29 -0
  8. package/dist/button.d.ts +11 -0
  9. package/dist/button.js +16 -0
  10. package/dist/chart.d.ts +48 -0
  11. package/dist/chart.js +126 -0
  12. package/dist/commands.d.ts +47 -0
  13. package/dist/commands.js +27 -0
  14. package/dist/content.d.ts +59 -0
  15. package/dist/content.js +104 -0
  16. package/dist/context-overlays.d.ts +95 -0
  17. package/dist/context-overlays.js +50 -0
  18. package/dist/data.d.ts +95 -0
  19. package/dist/data.js +40 -0
  20. package/dist/date-picker.d.ts +23 -0
  21. package/dist/date-picker.js +39 -0
  22. package/dist/dialog-focus.d.ts +5 -0
  23. package/dist/dialog-focus.js +35 -0
  24. package/dist/dialog.d.ts +59 -0
  25. package/dist/dialog.js +49 -0
  26. package/dist/feedback.d.ts +76 -0
  27. package/dist/feedback.js +35 -0
  28. package/dist/file-upload.d.ts +77 -0
  29. package/dist/file-upload.js +344 -0
  30. package/dist/filter-bar.d.ts +17 -0
  31. package/dist/filter-bar.js +23 -0
  32. package/dist/form-state.d.ts +5 -0
  33. package/dist/form-state.js +63 -0
  34. package/dist/forms.d.ts +91 -0
  35. package/dist/forms.js +109 -0
  36. package/dist/foundations.d.ts +51 -0
  37. package/dist/foundations.js +55 -0
  38. package/dist/icon.d.ts +37 -0
  39. package/dist/icon.js +28 -0
  40. package/dist/index.d.ts +37 -0
  41. package/dist/index.js +38 -0
  42. package/dist/input-extensions.d.ts +117 -0
  43. package/dist/input-extensions.js +64 -0
  44. package/dist/input.d.ts +7 -0
  45. package/dist/input.js +10 -0
  46. package/dist/markdown.d.ts +15 -0
  47. package/dist/markdown.js +121 -0
  48. package/dist/menu.d.ts +79 -0
  49. package/dist/menu.js +44 -0
  50. package/dist/messages.d.ts +63 -0
  51. package/dist/messages.js +38 -0
  52. package/dist/navigation-menu.d.ts +52 -0
  53. package/dist/navigation-menu.js +31 -0
  54. package/dist/navigation.d.ts +107 -0
  55. package/dist/navigation.js +108 -0
  56. package/dist/otp-field.d.ts +12 -0
  57. package/dist/otp-field.js +46 -0
  58. package/dist/pagination.d.ts +23 -0
  59. package/dist/pagination.js +44 -0
  60. package/dist/panels.d.ts +66 -0
  61. package/dist/panels.js +99 -0
  62. package/dist/parts.d.ts +9 -0
  63. package/dist/parts.js +22 -0
  64. package/dist/popover.d.ts +21 -0
  65. package/dist/popover.js +13 -0
  66. package/dist/scroll-area.d.ts +8 -0
  67. package/dist/scroll-area.js +39 -0
  68. package/dist/select.d.ts +33 -0
  69. package/dist/select.js +97 -0
  70. package/dist/sizing.d.ts +13 -0
  71. package/dist/sizing.js +23 -0
  72. package/dist/slot.d.ts +2 -0
  73. package/dist/slot.js +10 -0
  74. package/dist/status.d.ts +22 -0
  75. package/dist/status.js +17 -0
  76. package/dist/syntax-engine.d.ts +5 -0
  77. package/dist/syntax-engine.js +129 -0
  78. package/dist/syntax-highlighter.d.ts +17 -0
  79. package/dist/syntax-highlighter.js +64 -0
  80. package/dist/toast.d.ts +59 -0
  81. package/dist/toast.js +99 -0
  82. package/dist/tone-transition.d.ts +4 -0
  83. package/dist/tone-transition.js +81 -0
  84. package/dist/tooltip.d.ts +11 -0
  85. package/dist/tooltip.js +13 -0
  86. package/dist/tree.d.ts +28 -0
  87. package/dist/tree.js +311 -0
  88. package/package.json +109 -4
  89. package/src/styles.css +3799 -0
@@ -0,0 +1,81 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+ /** Animate a scalar so the color-mix expression, not a browser's default
4
+ * color interpolation space, determines every intermediate status color. */
5
+ export function useToneTransition(tone, forwardedRef) {
6
+ const ref = React.useRef(null);
7
+ React.useImperativeHandle(forwardedRef, () => ref.current, []);
8
+ const previous = React.useRef(undefined);
9
+ const paint = React.useRef([]);
10
+ const animation = React.useRef(null);
11
+ const read = (element) => {
12
+ const style = getComputedStyle(element);
13
+ return [style.backgroundColor, style.color, style.borderTopColor];
14
+ };
15
+ const clear = React.useCallback(() => {
16
+ animation.current?.cancel();
17
+ animation.current = null;
18
+ const element = ref.current;
19
+ element?.removeAttribute('data-status-transition');
20
+ for (let i = 0; i < 3; i++) {
21
+ element?.style.removeProperty(`--pui-status-from-${i}`);
22
+ element?.style.removeProperty(`--pui-status-to-${i}`);
23
+ }
24
+ }, []);
25
+ React.useLayoutEffect(() => {
26
+ const element = ref.current;
27
+ if (!element)
28
+ return;
29
+ const from = animation.current ? read(element) : paint.current;
30
+ clear();
31
+ const to = read(element);
32
+ const changed = previous.current !== undefined && previous.current !== tone;
33
+ previous.current = tone;
34
+ paint.current = to;
35
+ if (!changed ||
36
+ !element.animate ||
37
+ matchMedia('(prefers-reduced-motion: reduce), (forced-colors: active)')
38
+ .matches ||
39
+ document.documentElement.hasAttribute('data-theme-switching'))
40
+ return;
41
+ const durationToken = getComputedStyle(element)
42
+ .getPropertyValue('--motion-feedback')
43
+ .trim();
44
+ const duration = parseFloat(durationToken) * (durationToken.endsWith('ms') ? 1 : 1000);
45
+ if (!Number.isFinite(duration) || duration <= 0)
46
+ return;
47
+ from.forEach((value, i) => element.style.setProperty(`--pui-status-from-${i}`, value));
48
+ to.forEach((value, i) => element.style.setProperty(`--pui-status-to-${i}`, value));
49
+ element.setAttribute('data-status-transition', '');
50
+ animation.current = element.animate([{ '--pui-status-progress': '0' }, { '--pui-status-progress': '1' }], {
51
+ duration,
52
+ easing: getComputedStyle(element).getPropertyValue('--ease-out').trim() ||
53
+ 'ease-out',
54
+ });
55
+ animation.current.onfinish = clear;
56
+ }, [tone, clear]);
57
+ React.useLayoutEffect(() => {
58
+ const refresh = () => {
59
+ clear();
60
+ if (ref.current)
61
+ paint.current = read(ref.current);
62
+ };
63
+ const observer = new MutationObserver(refresh);
64
+ observer.observe(document.documentElement, {
65
+ attributes: true,
66
+ attributeFilter: ['data-theme', 'data-theme-switching'],
67
+ });
68
+ const media = [
69
+ matchMedia('(prefers-reduced-motion: reduce)'),
70
+ matchMedia('(forced-colors: active)'),
71
+ matchMedia('(prefers-color-scheme: light)'),
72
+ ];
73
+ media.forEach((query) => query.addEventListener('change', refresh));
74
+ return () => {
75
+ observer.disconnect();
76
+ media.forEach((query) => query.removeEventListener('change', refresh));
77
+ clear();
78
+ };
79
+ }, [clear]);
80
+ return ref;
81
+ }
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ export declare function TooltipProvider({ delayDuration, skipDelayDuration, children, }: {
3
+ delayDuration?: number;
4
+ skipDelayDuration?: number;
5
+ children: React.ReactNode;
6
+ }): React.JSX.Element;
7
+ export declare function Tooltip({ label, enabled, children, }: {
8
+ label: string;
9
+ enabled?: boolean;
10
+ children: React.ReactElement;
11
+ }): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useComponentSize } from './sizing.js';
4
+ import { Tooltip as BaseTooltip } from '@base-ui/react/tooltip';
5
+ export function TooltipProvider({ delayDuration = 150, skipDelayDuration = 300, children, }) {
6
+ return (_jsx(BaseTooltip.Provider, { delay: delayDuration, closeDelay: 0, timeout: skipDelayDuration, children: children }));
7
+ }
8
+ export function Tooltip({ label, enabled = true, children, }) {
9
+ const scheme = useComponentSize();
10
+ if (!enabled)
11
+ return children;
12
+ return (_jsxs(BaseTooltip.Root, { children: [_jsx(BaseTooltip.Trigger, { render: children }), _jsx(BaseTooltip.Portal, { children: _jsx(BaseTooltip.Positioner, { side: 'right', sideOffset: 10, collisionPadding: 8, className: 'pui-positioner pui-tooltip-positioner', children: _jsx(BaseTooltip.Popup, { className: 'pui-tooltip', "data-ui-size": scheme, children: label }) }) })] }));
13
+ }
package/dist/tree.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import * as React from 'react';
2
+ import { type ComponentSize } from './sizing.js';
3
+ export type TreeNode = {
4
+ id: string;
5
+ label: string;
6
+ children?: readonly TreeNode[];
7
+ disabled?: boolean;
8
+ };
9
+ export type TreeProps = {
10
+ nodes: readonly TreeNode[];
11
+ label: string;
12
+ expandedIds?: readonly string[];
13
+ defaultExpandedIds?: readonly string[];
14
+ onExpandedIdsChange?: (ids: string[]) => void;
15
+ selectedIds?: readonly string[];
16
+ defaultSelectedIds?: readonly string[];
17
+ onSelectedIdsChange?: (ids: string[]) => void;
18
+ selectionMode?: 'single' | 'multiple';
19
+ height?: number;
20
+ rowHeight?: number;
21
+ size?: ComponentSize;
22
+ virtualized?: boolean;
23
+ emptyLabel?: string;
24
+ dir?: 'ltr' | 'rtl';
25
+ className?: string;
26
+ };
27
+ /** Loaded, immutable hierarchy. Remote loading, permission checks and mutations remain caller-owned. */
28
+ export declare function Tree({ nodes, label, expandedIds, defaultExpandedIds, onExpandedIdsChange, selectedIds, defaultSelectedIds, onSelectedIdsChange, selectionMode, height, rowHeight: explicitRowHeight, size, virtualized, emptyLabel, dir, className, }: TreeProps): React.JSX.Element;
package/dist/tree.js ADDED
@@ -0,0 +1,311 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createElement as _createElement } from "react";
4
+ import * as React from 'react';
5
+ import { useComponentSize } from './sizing.js';
6
+ import { buildProxiedInstance, syncDataLoaderFeature, } from '@headless-tree/core';
7
+ import { useTree } from '@headless-tree/react';
8
+ import { defaultRangeExtractor, useVirtualizer, } from '@tanstack/react-virtual';
9
+ import { ScrollArea } from './scroll-area.js';
10
+ const normalize = (text) => text.normalize('NFKC').toLocaleLowerCase();
11
+ /** Loaded, immutable hierarchy. Remote loading, permission checks and mutations remain caller-owned. */
12
+ export function Tree({ nodes, label, expandedIds, defaultExpandedIds = [], onExpandedIdsChange, selectedIds, defaultSelectedIds = [], onSelectedIdsChange, selectionMode = 'single', height = 320, rowHeight: explicitRowHeight, size, virtualized = true, emptyLabel = 'No items.', dir, className, }) {
13
+ const scheme = useComponentSize(size);
14
+ const touchFloor = React.useSyncExternalStore((listener) => {
15
+ const query = matchMedia('(max-width: 760px), (pointer: coarse)');
16
+ query.addEventListener('change', listener);
17
+ return () => query.removeEventListener('change', listener);
18
+ }, () => matchMedia('(max-width: 760px), (pointer: coarse)').matches, () => false);
19
+ const rowHeight = explicitRowHeight ??
20
+ Math.max(touchFloor && scheme ? 44 : 0, scheme === 'small' ? 36 : scheme === 'large' ? 52 : 44);
21
+ const rootId = React.useId();
22
+ const model = React.useMemo(() => {
23
+ const data = new Map();
24
+ const parents = new Map();
25
+ function add(children, parent) {
26
+ for (const node of children) {
27
+ if (data.has(node.id) || node.id === rootId)
28
+ throw new Error(`Tree node IDs must be unique: ${node.id}`);
29
+ data.set(node.id, node);
30
+ parents.set(node.id, parent);
31
+ if (node.children)
32
+ add(node.children, node.id);
33
+ }
34
+ }
35
+ add(nodes, rootId);
36
+ data.set(rootId, { id: rootId, label, children: nodes });
37
+ return { data, parents };
38
+ }, [nodes, rootId, label]);
39
+ const [localExpanded, setLocalExpanded] = React.useState(defaultExpandedIds);
40
+ const [localSelected, setLocalSelected] = React.useState(defaultSelectedIds);
41
+ const [localFocus, setLocalFocus] = React.useState(() => (selectedIds ?? defaultSelectedIds)[0] ?? null);
42
+ const expanded = React.useMemo(() => [...new Set(expandedIds ?? localExpanded)].filter((id) => model.data.get(id)?.children?.length), [expandedIds, localExpanded, model]);
43
+ const selected = React.useMemo(() => {
44
+ const ids = [...new Set(selectedIds ?? localSelected)].filter((id) => model.data.has(id) && !model.data.get(id)?.disabled);
45
+ return selectionMode === 'single' ? ids.slice(-1) : ids;
46
+ }, [selectedIds, localSelected, model, selectionMode]);
47
+ const visibleIds = React.useMemo(() => {
48
+ const result = [], open = new Set(expanded);
49
+ function visit(children) {
50
+ for (const node of children) {
51
+ result.push(node.id);
52
+ if (open.has(node.id) && node.children)
53
+ visit(node.children);
54
+ }
55
+ }
56
+ visit(nodes);
57
+ return result;
58
+ }, [nodes, expanded]);
59
+ const focused = React.useMemo(() => {
60
+ const visible = new Set(visibleIds);
61
+ let id = localFocus;
62
+ while (id && !visible.has(id))
63
+ id = model.parents.get(id) ?? null;
64
+ return id ?? visibleIds[0] ?? null;
65
+ }, [localFocus, model, visibleIds]);
66
+ const viewport = React.useRef(null), container = React.useRef(null);
67
+ const virtualizerRef = React.useRef(null);
68
+ const pendingFocus = React.useRef(null), focusWithin = React.useRef(false), anchor = React.useRef(null);
69
+ const typeahead = React.useRef({ text: '', time: 0 });
70
+ function changeExpanded(ids) {
71
+ const next = [...new Set(ids)];
72
+ if (expandedIds === undefined)
73
+ setLocalExpanded(next);
74
+ onExpandedIdsChange?.(next);
75
+ }
76
+ function changeSelected(ids) {
77
+ const allowed = [...new Set(ids)].filter((id) => model.data.has(id) && !model.data.get(id)?.disabled);
78
+ const next = selectionMode === 'single' ? allowed.slice(-1) : allowed;
79
+ if (selectedIds === undefined)
80
+ setLocalSelected(next);
81
+ onSelectedIdsChange?.(next);
82
+ }
83
+ const tree = useTree({
84
+ rootItemId: rootId,
85
+ instanceBuilder: buildProxiedInstance,
86
+ features: [syncDataLoaderFeature],
87
+ getItemName: (item) => item.getItemData().label,
88
+ isItemFolder: (item) => !!item.getItemData().children?.length,
89
+ dataLoader: {
90
+ getItem: (id) => model.data.get(id),
91
+ getChildren: (id) => model.data.get(id)?.children?.map((node) => node.id) ?? [],
92
+ },
93
+ state: { expandedItems: expanded, focusedItem: focused },
94
+ setExpandedItems: (update) => changeExpanded(typeof update === 'function' ? update(expanded) : update),
95
+ setFocusedItem: (update) => setLocalFocus(typeof update === 'function' ? update(focused) : update),
96
+ scrollToItem: (item) => virtualizerRef.current?.scrollToIndex(item.getItemMeta().index, {
97
+ align: 'auto',
98
+ }),
99
+ });
100
+ const items = tree.getItems();
101
+ const focusedIndex = visibleIds.indexOf(focused ?? '');
102
+ const virtualizer = useVirtualizer({
103
+ count: items.length,
104
+ getScrollElement: () => viewport.current,
105
+ estimateSize: () => rowHeight,
106
+ getItemKey: (index) => items[index].getId(),
107
+ overscan: 4,
108
+ enabled: virtualized,
109
+ initialRect: { height, width: 0 },
110
+ rangeExtractor: (range) => [
111
+ ...new Set([
112
+ ...defaultRangeExtractor(range),
113
+ ...(focusedIndex >= 0 ? [focusedIndex] : []),
114
+ ]),
115
+ ].sort((a, b) => a - b),
116
+ });
117
+ virtualizerRef.current = virtualizer;
118
+ function focusItem(item) {
119
+ if (!item)
120
+ return;
121
+ pendingFocus.current = item.getId();
122
+ item.setFocused();
123
+ if (virtualized)
124
+ virtualizer.scrollToIndex(item.getItemMeta().index, { align: 'auto' });
125
+ else if (viewport.current) {
126
+ const top = item.getItemMeta().index * rowHeight;
127
+ if (top < viewport.current.scrollTop)
128
+ viewport.current.scrollTop = top;
129
+ else if (top + rowHeight >
130
+ viewport.current.scrollTop + viewport.current.clientHeight)
131
+ viewport.current.scrollTop =
132
+ top + rowHeight - viewport.current.clientHeight;
133
+ }
134
+ const element = item.getElement();
135
+ if (element) {
136
+ pendingFocus.current = null;
137
+ element.focus({ preventScroll: true });
138
+ }
139
+ }
140
+ React.useLayoutEffect(() => {
141
+ const needsRecovery = focusWithin.current && document.activeElement === document.body;
142
+ const id = pendingFocus.current && visibleIds.includes(pendingFocus.current)
143
+ ? pendingFocus.current
144
+ : needsRecovery
145
+ ? focused
146
+ : null;
147
+ if (id) {
148
+ const item = tree.getItemInstance(id);
149
+ item.getElement()?.focus({ preventScroll: true });
150
+ pendingFocus.current = null;
151
+ }
152
+ }, [focused, items, tree]);
153
+ React.useEffect(() => {
154
+ virtualizer.measure();
155
+ }, [rowHeight, virtualizer]);
156
+ function select(item, range = false) {
157
+ const id = item.getId();
158
+ if (item.getItemData().disabled)
159
+ return;
160
+ if (selectionMode === 'single')
161
+ changeSelected([id]);
162
+ else if (range && anchor.current && visibleIds.includes(anchor.current)) {
163
+ const a = visibleIds.indexOf(anchor.current), b = visibleIds.indexOf(id);
164
+ changeSelected(visibleIds.slice(Math.min(a, b), Math.max(a, b) + 1));
165
+ }
166
+ else
167
+ changeSelected(selected.includes(id)
168
+ ? selected.filter((key) => key !== id)
169
+ : [...selected, id]);
170
+ if (!range)
171
+ anchor.current = id;
172
+ }
173
+ function keydown(event) {
174
+ if (event.defaultPrevented ||
175
+ event.nativeEvent.isComposing ||
176
+ event.target.closest('button'))
177
+ return;
178
+ const targetId = event.target.closest('[data-tree-id]')?.dataset.treeId;
179
+ const item = targetId
180
+ ? tree.getItemInstance(targetId)
181
+ : focused
182
+ ? tree.getItemInstance(focused)
183
+ : items[0];
184
+ if (!item)
185
+ return;
186
+ if ([
187
+ 'ArrowDown',
188
+ 'ArrowUp',
189
+ 'ArrowLeft',
190
+ 'ArrowRight',
191
+ 'Home',
192
+ 'End',
193
+ ].includes(event.key))
194
+ typeahead.current = { text: '', time: 0 };
195
+ const index = item.getItemMeta().index;
196
+ const rtl = getComputedStyle(event.currentTarget).direction === 'rtl';
197
+ const forward = rtl ? 'ArrowLeft' : 'ArrowRight', backward = rtl ? 'ArrowRight' : 'ArrowLeft';
198
+ if ((event.ctrlKey || event.metaKey) &&
199
+ event.key.toLowerCase() === 'a' &&
200
+ selectionMode === 'multiple') {
201
+ event.preventDefault();
202
+ changeSelected(visibleIds);
203
+ return;
204
+ }
205
+ if (event.ctrlKey || event.metaKey || event.altKey)
206
+ return;
207
+ if ([
208
+ 'ArrowDown',
209
+ 'ArrowUp',
210
+ 'Home',
211
+ 'End',
212
+ forward,
213
+ backward,
214
+ 'Enter',
215
+ ' ',
216
+ '*',
217
+ ].includes(event.key))
218
+ event.preventDefault();
219
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
220
+ const next = items[index + (event.key === 'ArrowDown' ? 1 : -1)];
221
+ focusItem(next);
222
+ if (event.shiftKey && selectionMode === 'multiple' && next)
223
+ select(next, true);
224
+ }
225
+ else if (event.key === 'Home')
226
+ focusItem(items[0]);
227
+ else if (event.key === 'End')
228
+ focusItem(items.at(-1));
229
+ else if (event.key === forward &&
230
+ !item.getItemData().disabled &&
231
+ item.isFolder()) {
232
+ if (item.isExpanded())
233
+ focusItem(items[index + 1]);
234
+ else
235
+ item.expand();
236
+ }
237
+ else if (event.key === backward) {
238
+ if (item.isFolder() && item.isExpanded() && !item.getItemData().disabled)
239
+ item.collapse();
240
+ else if (item.getItemMeta().level > 0)
241
+ focusItem(item.getParent());
242
+ }
243
+ else if (event.key === 'Enter' || event.key === ' ')
244
+ select(item, event.shiftKey);
245
+ else if (event.key === '*')
246
+ changeExpanded([
247
+ ...expanded,
248
+ ...items
249
+ .filter((sibling) => sibling.getItemMeta().parentId === item.getItemMeta().parentId &&
250
+ sibling.isFolder() &&
251
+ !sibling.getItemData().disabled)
252
+ .map((sibling) => sibling.getId()),
253
+ ]);
254
+ else if (event.key.length === 1) {
255
+ const now = performance.now(), char = normalize(event.key), previous = now - typeahead.current.time < 700 ? typeahead.current.text : '';
256
+ const prefix = previous === char ? char : previous + char;
257
+ typeahead.current = { text: prefix, time: now };
258
+ for (let offset = 1; offset <= items.length; offset++) {
259
+ const next = items[(index + offset) % items.length];
260
+ if (normalize(next.getItemName()).startsWith(prefix)) {
261
+ event.preventDefault();
262
+ focusItem(next);
263
+ break;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ const containerProps = tree.getContainerProps(label);
269
+ const rows = virtualized
270
+ ? virtualizer
271
+ .getVirtualItems()
272
+ .map((row) => ({ index: row.index, start: row.start }))
273
+ : items.map((_, index) => ({ index, start: index * rowHeight }));
274
+ return (_jsx(ScrollArea, { orientation: 'vertical', label: `${label} scroll area`, tabIndex: -1, ref: viewport, scrollChain: 'contain', rootClassName: ['pui-tree-scroll', className].filter(Boolean).join(' '), dir: dir, rootStyle: { blockSize: height, direction: dir }, className: 'pui-tree-viewport', children: items.length ? (_jsx("div", { ...containerProps, ref: (element) => {
275
+ container.current = element;
276
+ containerProps.ref(element);
277
+ }, dir: dir, className: 'pui-tree', "data-ui-size": scheme, "aria-multiselectable": selectionMode === 'multiple' || undefined, onKeyDown: keydown, onFocusCapture: () => {
278
+ focusWithin.current = true;
279
+ }, onBlurCapture: (event) => {
280
+ if (event.relatedTarget &&
281
+ !event.currentTarget.contains(event.relatedTarget)) {
282
+ focusWithin.current = false;
283
+ typeahead.current.text = '';
284
+ }
285
+ }, style: {
286
+ blockSize: virtualized
287
+ ? virtualizer.getTotalSize()
288
+ : items.length * rowHeight,
289
+ }, children: rows.map(({ index, start }) => {
290
+ const item = items[index], node = item.getItemData(), props = item.getProps();
291
+ return (_createElement("div", { ...props, key: item.getId(), className: 'pui-tree-item', "data-tree-id": node.id, "aria-disabled": node.disabled || undefined, "aria-selected": node.disabled ? undefined : selected.includes(node.id), title: node.label, onFocus: () => item.setFocused(), onClick: (event) => {
292
+ focusItem(item);
293
+ select(item, event.shiftKey);
294
+ }, style: {
295
+ position: 'absolute',
296
+ insetInline: 0,
297
+ top: start,
298
+ blockSize: rowHeight,
299
+ paddingInlineStart: `calc(var(--space-2) + ${Math.min(item.getItemMeta().level, 8)} * var(--space-4))`,
300
+ } },
301
+ item.isFolder() ? (_jsx("button", { type: 'button', className: 'pui-tree-toggle', tabIndex: -1, disabled: node.disabled, "aria-label": `${item.isExpanded() ? 'Collapse' : 'Expand'} ${node.label}`, onClick: (event) => {
302
+ event.stopPropagation();
303
+ focusItem(item);
304
+ if (item.isExpanded())
305
+ item.collapse();
306
+ else
307
+ item.expand();
308
+ }, children: _jsx("svg", { "aria-hidden": 'true', width: '14', height: '14', viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: '1.6', "data-expanded": item.isExpanded() || undefined, children: _jsx("path", { d: 'm9 5 7 7-7 7' }) }) })) : (_jsx("span", { className: 'pui-tree-leaf', "aria-hidden": 'true' })),
309
+ _jsx("span", { className: 'pui-tree-label', children: node.label })));
310
+ }) })) : (_jsx("div", { role: 'status', className: 'pui-tree-empty', children: emptyLabel })) }));
311
+ }
package/package.json CHANGED
@@ -1,6 +1,111 @@
1
1
  {
2
2
  "name": "@surflane/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Surflane React components built on Base UI, with data, chart, code and Markdown components.",
5
+ "keywords": [
6
+ "design-system",
7
+ "react",
8
+ "components",
9
+ "base-ui",
10
+ "accessibility",
11
+ "react-server-components",
12
+ "surflane"
13
+ ],
14
+ "homepage": "https://surflane.page",
15
+ "bugs": {
16
+ "url": "https://github.com/Ashon/surflane/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/Ashon/surflane.git",
21
+ "directory": "packages/ui"
22
+ },
23
+ "license": "MIT OR Apache-2.0",
24
+ "author": "ashon",
25
+ "type": "module",
26
+ "sideEffects": [
27
+ "**/*.css"
28
+ ],
29
+ "files": [
30
+ "dist",
31
+ "src/styles.css",
32
+ "LICENSE-MIT",
33
+ "LICENSE-APACHE"
34
+ ],
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ },
40
+ "./styles.css": "./src/styles.css",
41
+ "./button": {
42
+ "types": "./dist/button.d.ts",
43
+ "import": "./dist/button.js"
44
+ },
45
+ "./slot": {
46
+ "types": "./dist/slot.d.ts",
47
+ "import": "./dist/slot.js"
48
+ },
49
+ "./input": {
50
+ "types": "./dist/input.d.ts",
51
+ "import": "./dist/input.js"
52
+ },
53
+ "./select": {
54
+ "types": "./dist/select.d.ts",
55
+ "import": "./dist/select.js"
56
+ },
57
+ "./popover": {
58
+ "types": "./dist/popover.d.ts",
59
+ "import": "./dist/popover.js"
60
+ },
61
+ "./tooltip": {
62
+ "types": "./dist/tooltip.d.ts",
63
+ "import": "./dist/tooltip.js"
64
+ },
65
+ "./dialog": {
66
+ "types": "./dist/dialog.d.ts",
67
+ "import": "./dist/dialog.js"
68
+ },
69
+ "./sizing": {
70
+ "types": "./dist/sizing.d.ts",
71
+ "import": "./dist/sizing.js"
72
+ },
73
+ "./chart": {
74
+ "types": "./dist/chart.d.ts",
75
+ "import": "./dist/chart.js"
76
+ },
77
+ "./markdown": {
78
+ "types": "./dist/markdown.d.ts",
79
+ "import": "./dist/markdown.js"
80
+ },
81
+ "./syntax-highlighter": {
82
+ "types": "./dist/syntax-highlighter.d.ts",
83
+ "import": "./dist/syntax-highlighter.js"
84
+ },
85
+ "./package.json": "./package.json"
86
+ },
87
+ "scripts": {
88
+ "build": "tsc -p tsconfig.json",
89
+ "prepack": "npm run build"
90
+ },
91
+ "dependencies": {
92
+ "@base-ui/react": "1.9.0",
93
+ "@headless-tree/core": "1.8.0",
94
+ "@headless-tree/react": "1.8.0",
95
+ "@internationalized/date": "3.12.4",
96
+ "@tanstack/react-virtual": "3.14.21",
97
+ "react-aria-components": "1.22.1",
98
+ "react-markdown": "10.1.0",
99
+ "recharts": "3.10.1",
100
+ "remark-gfm": "4.0.1",
101
+ "shiki": "4.5.0"
102
+ },
103
+ "peerDependencies": {
104
+ "@surflane/design-tokens": "^0.2.0",
105
+ "react": "^19.0.0",
106
+ "react-dom": "^19.0.0"
107
+ },
108
+ "publishConfig": {
109
+ "access": "public"
110
+ }
111
+ }