@stll/folio-react 0.0.1-placeholder.0 → 0.1.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.
@@ -0,0 +1,483 @@
1
+ import { createContext, useContext } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Checkbox } from "@base-ui/react/checkbox";
4
+ import { Popover } from "@base-ui/react/popover";
5
+ import { Dialog } from "@base-ui/react/dialog";
6
+ import { Input } from "@base-ui/react/input";
7
+ import { Menu } from "@base-ui/react/menu";
8
+ import { Select } from "@base-ui/react/select";
9
+ //#region src/lib/utils.ts
10
+ /** Merge class names, filtering out falsy values. */
11
+ function cn(...inputs) {
12
+ return inputs.filter(Boolean).join(" ");
13
+ }
14
+ //#endregion
15
+ //#region src/ui/defaults/button.tsx
16
+ /**
17
+ * Built-in, dependency-free Button used when a consumer does not inject one.
18
+ *
19
+ * Renders a native, accessible `<button>` honoring the `FolioButtonProps`
20
+ * subset. The minimal default styling lives in `editor.css`
21
+ * (`.folio-default-button*` classes, keyed off folio's `--doc-*` variables with
22
+ * safe fallbacks) rather than inline styles, so a caller's `className` can
23
+ * override the defaults through the normal cascade. Consumers that want
24
+ * polished chrome inject their own design-system Button via `DocxEditor`'s
25
+ * `components` prop.
26
+ */
27
+ function DefaultButton({ variant = "default", size = "sm", className, children, ...props }) {
28
+ return /* @__PURE__ */ jsx("button", {
29
+ type: "button",
30
+ className: cn("folio-default-button", `folio-default-button--${variant}`, `folio-default-button--${size}`, className),
31
+ ...props,
32
+ children
33
+ });
34
+ }
35
+ //#endregion
36
+ //#region src/ui/defaults/checkbox.tsx
37
+ /**
38
+ * Built-in, dependency-light Checkbox used when a consumer does not inject one.
39
+ * Wraps `@base-ui/react`'s accessible Checkbox primitive with a minimal check
40
+ * indicator; consumers inject their own design-system Checkbox for full polish.
41
+ */
42
+ function DefaultCheckbox({ className, ...props }) {
43
+ return /* @__PURE__ */ jsx(Checkbox.Root, {
44
+ className: cn("folio-default-checkbox", className),
45
+ ...props,
46
+ children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
47
+ className: "folio-default-checkbox-indicator",
48
+ children: /* @__PURE__ */ jsx("svg", {
49
+ "aria-hidden": "true",
50
+ fill: "none",
51
+ height: "14",
52
+ stroke: "currentColor",
53
+ strokeLinecap: "round",
54
+ strokeLinejoin: "round",
55
+ strokeWidth: "3",
56
+ viewBox: "0 0 24 24",
57
+ width: "14",
58
+ xmlns: "http://www.w3.org/2000/svg",
59
+ children: /* @__PURE__ */ jsx("path", { d: "M5.252 12.7 10.2 18.63 18.748 5.37" })
60
+ })
61
+ })
62
+ });
63
+ }
64
+ //#endregion
65
+ //#region src/ui/defaults/color-picker.tsx
66
+ /**
67
+ * Built-in, dependency-light ColorPicker used when a consumer does not inject
68
+ * one. Wraps `@base-ui/react`'s Popover primitive (focus management,
69
+ * portalling, collision-aware positioning) and renders the `presets` as a row
70
+ * of swatch buttons plus a native `<input type="color">` for a custom color.
71
+ * `onSelect` emits a preset value or a 6-char uppercase hex (no `#`), matching
72
+ * the design-system ColorPicker contract. Consumers inject a polished picker
73
+ * via `DocxEditor`'s `components` prop.
74
+ */
75
+ const swatchColor = (preset) => preset.color ?? `#${preset.value}`;
76
+ const normalizeHex = (hex) => hex.replace(/^#/u, "").toUpperCase();
77
+ const EMPTY_PRESETS = [];
78
+ function DefaultColorPicker({ value, onSelect, onClear, presets = EMPTY_PRESETS, columns = 8, children }) {
79
+ return /* @__PURE__ */ jsxs(Popover.Root, { children: [/* @__PURE__ */ jsx(Popover.Trigger, {
80
+ nativeButton: false,
81
+ render: /* @__PURE__ */ jsx("div", { className: "folio-default-color-picker-trigger" }),
82
+ children
83
+ }), /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
84
+ align: "start",
85
+ className: "folio-default-popover-positioner",
86
+ side: "bottom",
87
+ sideOffset: 4,
88
+ children: /* @__PURE__ */ jsxs(Popover.Popup, {
89
+ className: "folio-default-color-picker-popup",
90
+ children: [
91
+ onClear && /* @__PURE__ */ jsx(Popover.Close, {
92
+ className: "folio-default-color-picker-clear",
93
+ onClick: onClear,
94
+ children: "No color"
95
+ }),
96
+ /* @__PURE__ */ jsx("div", {
97
+ className: "folio-default-color-picker-swatches",
98
+ style: { gridTemplateColumns: `repeat(${columns}, 1fr)` },
99
+ children: presets.map((preset) => /* @__PURE__ */ jsx(Popover.Close, {
100
+ "aria-label": preset.label,
101
+ className: cn("folio-default-color-picker-swatch", value === preset.value && "folio-default-color-picker-swatch--selected"),
102
+ onClick: () => onSelect?.(preset.value),
103
+ style: { backgroundColor: swatchColor(preset) },
104
+ title: preset.label
105
+ }, preset.value))
106
+ }),
107
+ /* @__PURE__ */ jsxs("label", {
108
+ className: "folio-default-color-picker-custom",
109
+ children: ["Custom", /* @__PURE__ */ jsx("input", {
110
+ "aria-label": "Custom color",
111
+ onChange: (event) => onSelect?.(normalizeHex(event.target.value)),
112
+ type: "color",
113
+ value: value ? `#${normalizeHex(value)}` : "#000000"
114
+ })]
115
+ })
116
+ ]
117
+ })
118
+ }) })] });
119
+ }
120
+ //#endregion
121
+ //#region src/ui/defaults/date-picker-popover.tsx
122
+ /**
123
+ * Built-in, dependency-light DatePickerPopover used when a consumer does not
124
+ * inject one. Renders a native `<input type="date">` whose value is an ISO
125
+ * `yyyy-mm-dd` string; `onChange` emits that string (or `null` when cleared),
126
+ * matching the design-system picker contract. Consumers inject a polished
127
+ * calendar via `DocxEditor`'s `components` prop.
128
+ */
129
+ const toISODate = (value) => {
130
+ if (value === null) return "";
131
+ if (value instanceof Date) return `${value.getFullYear().toString().padStart(4, "0")}-${(value.getMonth() + 1).toString().padStart(2, "0")}-${value.getDate().toString().padStart(2, "0")}`;
132
+ return value.length >= 10 ? value.slice(0, 10) : value;
133
+ };
134
+ function DefaultDatePickerPopover({ value, onChange, defaultOpen = false }) {
135
+ return /* @__PURE__ */ jsx("input", {
136
+ autoFocus: defaultOpen,
137
+ className: "folio-default-date-input",
138
+ onChange: (event) => onChange(event.target.value || null),
139
+ type: "date",
140
+ value: toISODate(value)
141
+ });
142
+ }
143
+ //#endregion
144
+ //#region src/ui/defaults/dialog.tsx
145
+ /**
146
+ * Built-in, dependency-light Dialog parts used when a consumer does not inject
147
+ * its own. Each part wraps the matching `@base-ui/react` primitive — the same
148
+ * accessible primitive the app design system wraps — with minimal styling.
149
+ * Folio's chrome positions the popup itself via `className`, so the default
150
+ * popup is a bare portalled surface (no viewport grid); consumers inject a
151
+ * polished Dialog through `DocxEditor`'s `components` prop.
152
+ */
153
+ function DefaultDialogRoot(props) {
154
+ return /* @__PURE__ */ jsx(Dialog.Root, { ...props });
155
+ }
156
+ function DefaultDialogPortal(props) {
157
+ return /* @__PURE__ */ jsx(Dialog.Portal, { ...props });
158
+ }
159
+ function DefaultDialogBackdrop({ className, ...props }) {
160
+ return /* @__PURE__ */ jsx(Dialog.Backdrop, {
161
+ className: cn("folio-default-dialog-backdrop", className),
162
+ ...props
163
+ });
164
+ }
165
+ function DefaultDialogPopup({ className, ...props }) {
166
+ return /* @__PURE__ */ jsx(Dialog.Popup, {
167
+ className: cn("folio-default-dialog-popup", className),
168
+ ...props
169
+ });
170
+ }
171
+ function DefaultDialogTitle({ className, ...props }) {
172
+ return /* @__PURE__ */ jsx(Dialog.Title, {
173
+ className: cn("folio-default-dialog-title", className),
174
+ ...props
175
+ });
176
+ }
177
+ function DefaultDialogClose({ className, ...props }) {
178
+ return /* @__PURE__ */ jsx(Dialog.Close, {
179
+ className: cn("folio-default-dialog-close", className),
180
+ ...props
181
+ });
182
+ }
183
+ const DefaultDialog = {
184
+ Root: DefaultDialogRoot,
185
+ Portal: DefaultDialogPortal,
186
+ Backdrop: DefaultDialogBackdrop,
187
+ Popup: DefaultDialogPopup,
188
+ Title: DefaultDialogTitle,
189
+ Close: DefaultDialogClose
190
+ };
191
+ //#endregion
192
+ //#region src/ui/defaults/input.tsx
193
+ /**
194
+ * Built-in, dependency-light Input used when a consumer does not inject one.
195
+ *
196
+ * Wraps `@base-ui/react`'s Input primitive, honoring the `nativeInput` escape
197
+ * hatch (render a plain `<input>` when the caller needs uncontrolled native
198
+ * behavior) and folio's `size` shorthand. Styling is intentionally minimal;
199
+ * consumers inject their own design-system Input for full polish.
200
+ */
201
+ function DefaultInput({ className, size = "default", nativeInput = false, ...props }) {
202
+ const inputClassName = cn("folio-default-input", className);
203
+ const numericSize = typeof size === "number" ? size : void 0;
204
+ if (nativeInput) return /* @__PURE__ */ jsx("input", {
205
+ className: inputClassName,
206
+ "data-size": typeof size === "string" ? size : void 0,
207
+ size: numericSize,
208
+ ...props
209
+ });
210
+ return /* @__PURE__ */ jsx(Input, {
211
+ className: inputClassName,
212
+ "data-size": typeof size === "string" ? size : void 0,
213
+ size: numericSize,
214
+ ...props
215
+ });
216
+ }
217
+ //#endregion
218
+ //#region src/ui/defaults/menu.tsx
219
+ /**
220
+ * Built-in, dependency-light Menu parts used when a consumer does not inject
221
+ * its own. Each part wraps the matching `@base-ui/react` primitive (real
222
+ * keyboard navigation, portalling, collision-aware positioning) with minimal
223
+ * styling. Consumers inject a polished Menu via `DocxEditor`'s `components`
224
+ * prop.
225
+ */
226
+ function DefaultMenuRoot(props) {
227
+ return /* @__PURE__ */ jsx(Menu.Root, { ...props });
228
+ }
229
+ function DefaultMenuTrigger({ nativeButton = true, ...props }) {
230
+ return /* @__PURE__ */ jsx(Menu.Trigger, {
231
+ nativeButton,
232
+ ...props
233
+ });
234
+ }
235
+ function DefaultMenuPopup({ className, children, align = "center", side = "bottom", sideOffset = 4, alignOffset, ...props }) {
236
+ return /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
237
+ align,
238
+ alignOffset,
239
+ className: "folio-default-menu-positioner",
240
+ side,
241
+ sideOffset,
242
+ children: /* @__PURE__ */ jsx(Menu.Popup, {
243
+ className: cn("folio-default-menu-popup", className),
244
+ ...props,
245
+ children
246
+ })
247
+ }) });
248
+ }
249
+ function DefaultMenuItem({ className, ...props }) {
250
+ return /* @__PURE__ */ jsx(Menu.Item, {
251
+ className: cn("folio-default-menu-item", className),
252
+ ...props
253
+ });
254
+ }
255
+ function DefaultMenuCheckboxItem({ className, children, ...props }) {
256
+ return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
257
+ className: cn("folio-default-menu-checkbox-item", className),
258
+ ...props,
259
+ children: [/* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, {
260
+ className: "folio-default-menu-checkbox-indicator",
261
+ children: /* @__PURE__ */ jsx("svg", {
262
+ "aria-hidden": "true",
263
+ fill: "none",
264
+ height: "14",
265
+ stroke: "currentColor",
266
+ strokeLinecap: "round",
267
+ strokeLinejoin: "round",
268
+ strokeWidth: "3",
269
+ viewBox: "0 0 24 24",
270
+ width: "14",
271
+ xmlns: "http://www.w3.org/2000/svg",
272
+ children: /* @__PURE__ */ jsx("path", { d: "M5.252 12.7 10.2 18.63 18.748 5.37" })
273
+ })
274
+ }), /* @__PURE__ */ jsx("span", { children })]
275
+ });
276
+ }
277
+ function DefaultMenuGroup(props) {
278
+ return /* @__PURE__ */ jsx(Menu.Group, { ...props });
279
+ }
280
+ function DefaultMenuGroupLabel({ className, ...props }) {
281
+ return /* @__PURE__ */ jsx(Menu.GroupLabel, {
282
+ className: cn("folio-default-menu-group-label", className),
283
+ ...props
284
+ });
285
+ }
286
+ function DefaultMenuSeparator({ className, ...props }) {
287
+ return /* @__PURE__ */ jsx(Menu.Separator, {
288
+ className: cn("folio-default-menu-separator", className),
289
+ ...props
290
+ });
291
+ }
292
+ const DefaultMenu = {
293
+ Root: DefaultMenuRoot,
294
+ Trigger: DefaultMenuTrigger,
295
+ Popup: DefaultMenuPopup,
296
+ Item: DefaultMenuItem,
297
+ CheckboxItem: DefaultMenuCheckboxItem,
298
+ Group: DefaultMenuGroup,
299
+ GroupLabel: DefaultMenuGroupLabel,
300
+ Separator: DefaultMenuSeparator
301
+ };
302
+ //#endregion
303
+ //#region src/ui/defaults/outline-rail.tsx
304
+ /**
305
+ * Built-in, dependency-light OutlineRail used when a consumer does not inject
306
+ * one. Renders the outline as a plain semantic `<nav>` list of clickable
307
+ * entries indented by level; clicking an entry calls `onJump` with the resolved
308
+ * scroll container. Consumers inject the polished tick-rail + hover panel via
309
+ * `DocxEditor`'s `components` prop.
310
+ */
311
+ function DefaultOutlineRail({ items, scrollContainerRef, onJump, activeId, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) {
312
+ if (items.length < 2) return null;
313
+ return /* @__PURE__ */ jsx("nav", {
314
+ "aria-label": ariaLabel,
315
+ className: "folio-default-outline-rail",
316
+ style: {
317
+ top: topOffset,
318
+ width: panelWidth
319
+ },
320
+ children: /* @__PURE__ */ jsx("ul", {
321
+ className: "folio-default-outline-list",
322
+ children: items.map((item) => {
323
+ const isActive = item.id === activeId;
324
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
325
+ "aria-current": isActive ? "true" : void 0,
326
+ className: cn("folio-default-outline-item", isActive && "folio-default-outline-item--active"),
327
+ onClick: () => {
328
+ const container = scrollContainerRef.current;
329
+ if (container) onJump(item.id, container);
330
+ },
331
+ style: { paddingInlineStart: `${item.level * 12 + 8}px` },
332
+ title: item.label,
333
+ type: "button",
334
+ children: item.label
335
+ }) }, item.id);
336
+ })
337
+ })
338
+ });
339
+ }
340
+ //#endregion
341
+ //#region src/ui/defaults/popover.tsx
342
+ /**
343
+ * Built-in, dependency-light Popover parts used when a consumer does not inject
344
+ * its own. Each part wraps the matching `@base-ui/react` primitive (real focus
345
+ * management, portalling, and collision-aware positioning) with minimal
346
+ * styling. Consumers inject a polished Popover via `DocxEditor`'s `components`
347
+ * prop.
348
+ */
349
+ function DefaultPopoverRoot(props) {
350
+ return /* @__PURE__ */ jsx(Popover.Root, { ...props });
351
+ }
352
+ function DefaultPopoverTrigger(props) {
353
+ return /* @__PURE__ */ jsx(Popover.Trigger, { ...props });
354
+ }
355
+ function DefaultPopoverPopup({ className, children, side = "bottom", align = "center", sideOffset = 4, alignOffset = 0, ...props }) {
356
+ return /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
357
+ align,
358
+ alignOffset,
359
+ className: "folio-default-popover-positioner",
360
+ side,
361
+ sideOffset,
362
+ children: /* @__PURE__ */ jsx(Popover.Popup, {
363
+ className: cn("folio-default-popover-popup", className),
364
+ ...props,
365
+ children
366
+ })
367
+ }) });
368
+ }
369
+ function DefaultPopoverClose(props) {
370
+ return /* @__PURE__ */ jsx(Popover.Close, { ...props });
371
+ }
372
+ const DefaultPopover = {
373
+ Root: DefaultPopoverRoot,
374
+ Trigger: DefaultPopoverTrigger,
375
+ Popup: DefaultPopoverPopup,
376
+ Close: DefaultPopoverClose
377
+ };
378
+ //#endregion
379
+ //#region src/ui/defaults/select.tsx
380
+ /**
381
+ * Built-in, dependency-light Select parts used when a consumer does not inject
382
+ * its own. Each part wraps the matching `@base-ui/react` primitive (real
383
+ * listbox semantics, keyboard navigation, portalled positioning) with minimal
384
+ * styling. The default popup omits the scroll arrows and item-aligned
385
+ * positioning of a fully styled design-system Select; consumers inject their
386
+ * own Select via `DocxEditor`'s `components` prop for that polish.
387
+ */
388
+ function DefaultSelectRoot(props) {
389
+ return /* @__PURE__ */ jsx(Select.Root, { ...props });
390
+ }
391
+ function DefaultSelectTrigger({ className, children, size, ...props }) {
392
+ return /* @__PURE__ */ jsxs(Select.Trigger, {
393
+ className: cn("folio-default-select-trigger", className),
394
+ "data-size": size,
395
+ ...props,
396
+ children: [children, /* @__PURE__ */ jsx(Select.Icon, {
397
+ className: "folio-default-select-icon",
398
+ children: /* @__PURE__ */ jsx("svg", {
399
+ "aria-hidden": "true",
400
+ fill: "none",
401
+ height: "16",
402
+ stroke: "currentColor",
403
+ strokeLinecap: "round",
404
+ strokeLinejoin: "round",
405
+ strokeWidth: "2",
406
+ viewBox: "0 0 24 24",
407
+ width: "16",
408
+ xmlns: "http://www.w3.org/2000/svg",
409
+ children: /* @__PURE__ */ jsx("path", { d: "m7 15 5 5 5-5M7 9l5-5 5 5" })
410
+ })
411
+ })]
412
+ });
413
+ }
414
+ function DefaultSelectValue({ className, ...props }) {
415
+ return /* @__PURE__ */ jsx(Select.Value, {
416
+ className: cn("folio-default-select-value", className),
417
+ ...props
418
+ });
419
+ }
420
+ function DefaultSelectPopup({ className, children, ...props }) {
421
+ return /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, {
422
+ align: "start",
423
+ className: "folio-default-select-positioner",
424
+ side: "bottom",
425
+ sideOffset: 4,
426
+ children: /* @__PURE__ */ jsx(Select.Popup, {
427
+ className: cn("folio-default-select-popup", className),
428
+ ...props,
429
+ children: /* @__PURE__ */ jsx(Select.List, { children })
430
+ })
431
+ }) });
432
+ }
433
+ function DefaultSelectItem({ className, children, ...props }) {
434
+ return /* @__PURE__ */ jsx(Select.Item, {
435
+ className: cn("folio-default-select-item", className),
436
+ ...props,
437
+ children: /* @__PURE__ */ jsx(Select.ItemText, { children })
438
+ });
439
+ }
440
+ //#endregion
441
+ //#region src/ui/folio-ui.tsx
442
+ const DEFAULT_COMPONENTS = {
443
+ Button: DefaultButton,
444
+ Dialog: DefaultDialog,
445
+ Select: {
446
+ Root: DefaultSelectRoot,
447
+ Trigger: DefaultSelectTrigger,
448
+ Value: DefaultSelectValue,
449
+ Popup: DefaultSelectPopup,
450
+ Item: DefaultSelectItem
451
+ },
452
+ Menu: DefaultMenu,
453
+ Popover: DefaultPopover,
454
+ Input: DefaultInput,
455
+ Checkbox: DefaultCheckbox,
456
+ ColorPicker: DefaultColorPicker,
457
+ DatePickerPopover: DefaultDatePickerPopover,
458
+ OutlineRail: DefaultOutlineRail
459
+ };
460
+ const FolioUIContext = createContext(DEFAULT_COMPONENTS);
461
+ /**
462
+ * Merge a consumer's partial overrides over the built-in defaults: every key is
463
+ * present (defaults fill the gaps), and each provided override wins. Pure, so
464
+ * the resolution contract is unit-tested independently of React.
465
+ */
466
+ function resolveFolioComponents(components) {
467
+ return components ? {
468
+ ...DEFAULT_COMPONENTS,
469
+ ...components
470
+ } : DEFAULT_COMPONENTS;
471
+ }
472
+ function FolioUIProvider({ components, children }) {
473
+ const value = resolveFolioComponents(components);
474
+ return /* @__PURE__ */ jsx(FolioUIContext.Provider, {
475
+ value,
476
+ children
477
+ });
478
+ }
479
+ function useFolioUI() {
480
+ return useContext(FolioUIContext);
481
+ }
482
+ //#endregion
483
+ export { cn as i, FolioUIProvider as n, useFolioUI as r, DEFAULT_COMPONENTS as t };