@stll/folio-react 0.4.0 → 0.5.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.
@@ -1,490 +0,0 @@
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 folio-root",
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({ children, ...props }) {
157
- return /* @__PURE__ */ jsx(Dialog.Portal, {
158
- ...props,
159
- children: /* @__PURE__ */ jsx("div", {
160
- className: "folio-root",
161
- style: { display: "contents" },
162
- children
163
- })
164
- });
165
- }
166
- function DefaultDialogBackdrop({ className, ...props }) {
167
- return /* @__PURE__ */ jsx(Dialog.Backdrop, {
168
- className: cn("folio-default-dialog-backdrop", className),
169
- ...props
170
- });
171
- }
172
- function DefaultDialogPopup({ className, ...props }) {
173
- return /* @__PURE__ */ jsx(Dialog.Popup, {
174
- className: cn("folio-default-dialog-popup", className),
175
- ...props
176
- });
177
- }
178
- function DefaultDialogTitle({ className, ...props }) {
179
- return /* @__PURE__ */ jsx(Dialog.Title, {
180
- className: cn("folio-default-dialog-title", className),
181
- ...props
182
- });
183
- }
184
- function DefaultDialogClose({ className, ...props }) {
185
- return /* @__PURE__ */ jsx(Dialog.Close, {
186
- className: cn("folio-default-dialog-close", className),
187
- ...props
188
- });
189
- }
190
- const DefaultDialog = {
191
- Root: DefaultDialogRoot,
192
- Portal: DefaultDialogPortal,
193
- Backdrop: DefaultDialogBackdrop,
194
- Popup: DefaultDialogPopup,
195
- Title: DefaultDialogTitle,
196
- Close: DefaultDialogClose
197
- };
198
- //#endregion
199
- //#region src/ui/defaults/input.tsx
200
- /**
201
- * Built-in, dependency-light Input used when a consumer does not inject one.
202
- *
203
- * Wraps `@base-ui/react`'s Input primitive, honoring the `nativeInput` escape
204
- * hatch (render a plain `<input>` when the caller needs uncontrolled native
205
- * behavior) and folio's `size` shorthand. Styling is intentionally minimal;
206
- * consumers inject their own design-system Input for full polish.
207
- */
208
- function DefaultInput({ className, size = "default", nativeInput = false, ...props }) {
209
- const inputClassName = cn("folio-default-input", className);
210
- const numericSize = typeof size === "number" ? size : void 0;
211
- if (nativeInput) return /* @__PURE__ */ jsx("input", {
212
- className: inputClassName,
213
- "data-size": typeof size === "string" ? size : void 0,
214
- size: numericSize,
215
- ...props
216
- });
217
- return /* @__PURE__ */ jsx(Input, {
218
- className: inputClassName,
219
- "data-size": typeof size === "string" ? size : void 0,
220
- size: numericSize,
221
- ...props
222
- });
223
- }
224
- //#endregion
225
- //#region src/ui/defaults/menu.tsx
226
- /**
227
- * Built-in, dependency-light Menu parts used when a consumer does not inject
228
- * its own. Each part wraps the matching `@base-ui/react` primitive (real
229
- * keyboard navigation, portalling, collision-aware positioning) with minimal
230
- * styling. Consumers inject a polished Menu via `DocxEditor`'s `components`
231
- * prop.
232
- */
233
- function DefaultMenuRoot(props) {
234
- return /* @__PURE__ */ jsx(Menu.Root, { ...props });
235
- }
236
- function DefaultMenuTrigger({ nativeButton = true, ...props }) {
237
- return /* @__PURE__ */ jsx(Menu.Trigger, {
238
- nativeButton,
239
- ...props
240
- });
241
- }
242
- function DefaultMenuPopup({ className, children, align = "center", side = "bottom", sideOffset = 4, alignOffset, ...props }) {
243
- return /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, {
244
- align,
245
- alignOffset,
246
- className: "folio-default-menu-positioner folio-root",
247
- side,
248
- sideOffset,
249
- children: /* @__PURE__ */ jsx(Menu.Popup, {
250
- className: cn("folio-default-menu-popup", className),
251
- ...props,
252
- children
253
- })
254
- }) });
255
- }
256
- function DefaultMenuItem({ className, ...props }) {
257
- return /* @__PURE__ */ jsx(Menu.Item, {
258
- className: cn("folio-default-menu-item", className),
259
- ...props
260
- });
261
- }
262
- function DefaultMenuCheckboxItem({ className, children, ...props }) {
263
- return /* @__PURE__ */ jsxs(Menu.CheckboxItem, {
264
- className: cn("folio-default-menu-checkbox-item", className),
265
- ...props,
266
- children: [/* @__PURE__ */ jsx(Menu.CheckboxItemIndicator, {
267
- className: "folio-default-menu-checkbox-indicator",
268
- children: /* @__PURE__ */ jsx("svg", {
269
- "aria-hidden": "true",
270
- fill: "none",
271
- height: "14",
272
- stroke: "currentColor",
273
- strokeLinecap: "round",
274
- strokeLinejoin: "round",
275
- strokeWidth: "3",
276
- viewBox: "0 0 24 24",
277
- width: "14",
278
- xmlns: "http://www.w3.org/2000/svg",
279
- children: /* @__PURE__ */ jsx("path", { d: "M5.252 12.7 10.2 18.63 18.748 5.37" })
280
- })
281
- }), /* @__PURE__ */ jsx("span", { children })]
282
- });
283
- }
284
- function DefaultMenuGroup(props) {
285
- return /* @__PURE__ */ jsx(Menu.Group, { ...props });
286
- }
287
- function DefaultMenuGroupLabel({ className, ...props }) {
288
- return /* @__PURE__ */ jsx(Menu.GroupLabel, {
289
- className: cn("folio-default-menu-group-label", className),
290
- ...props
291
- });
292
- }
293
- function DefaultMenuSeparator({ className, ...props }) {
294
- return /* @__PURE__ */ jsx(Menu.Separator, {
295
- className: cn("folio-default-menu-separator", className),
296
- ...props
297
- });
298
- }
299
- const DefaultMenu = {
300
- Root: DefaultMenuRoot,
301
- Trigger: DefaultMenuTrigger,
302
- Popup: DefaultMenuPopup,
303
- Item: DefaultMenuItem,
304
- CheckboxItem: DefaultMenuCheckboxItem,
305
- Group: DefaultMenuGroup,
306
- GroupLabel: DefaultMenuGroupLabel,
307
- Separator: DefaultMenuSeparator
308
- };
309
- //#endregion
310
- //#region src/ui/defaults/outline-rail.tsx
311
- /**
312
- * Built-in, dependency-light OutlineRail used when a consumer does not inject
313
- * one. Renders the outline as a plain semantic `<nav>` list of clickable
314
- * entries indented by level; clicking an entry calls `onJump` with the resolved
315
- * scroll container. Consumers inject the polished tick-rail + hover panel via
316
- * `DocxEditor`'s `components` prop.
317
- */
318
- function DefaultOutlineRail({ items, scrollContainerRef, onJump, activeId, topOffset = 0, panelWidth = 300, ariaLabel = "Outline" }) {
319
- if (items.length < 2) return null;
320
- return /* @__PURE__ */ jsx("nav", {
321
- "aria-label": ariaLabel,
322
- className: "folio-default-outline-rail",
323
- style: {
324
- top: topOffset,
325
- width: panelWidth
326
- },
327
- children: /* @__PURE__ */ jsx("ul", {
328
- className: "folio-default-outline-list",
329
- children: items.map((item) => {
330
- const isActive = item.id === activeId;
331
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
332
- "aria-current": isActive ? "true" : void 0,
333
- className: cn("folio-default-outline-item", isActive && "folio-default-outline-item--active"),
334
- onClick: () => {
335
- const container = scrollContainerRef.current;
336
- if (container) onJump(item.id, container);
337
- },
338
- style: { paddingInlineStart: `${item.level * 12 + 8}px` },
339
- title: item.label,
340
- type: "button",
341
- children: item.label
342
- }) }, item.id);
343
- })
344
- })
345
- });
346
- }
347
- //#endregion
348
- //#region src/ui/defaults/popover.tsx
349
- /**
350
- * Built-in, dependency-light Popover parts used when a consumer does not inject
351
- * its own. Each part wraps the matching `@base-ui/react` primitive (real focus
352
- * management, portalling, and collision-aware positioning) with minimal
353
- * styling. Consumers inject a polished Popover via `DocxEditor`'s `components`
354
- * prop.
355
- */
356
- function DefaultPopoverRoot(props) {
357
- return /* @__PURE__ */ jsx(Popover.Root, { ...props });
358
- }
359
- function DefaultPopoverTrigger(props) {
360
- return /* @__PURE__ */ jsx(Popover.Trigger, { ...props });
361
- }
362
- function DefaultPopoverPopup({ className, children, side = "bottom", align = "center", sideOffset = 4, alignOffset = 0, ...props }) {
363
- return /* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, {
364
- align,
365
- alignOffset,
366
- className: "folio-default-popover-positioner folio-root",
367
- side,
368
- sideOffset,
369
- children: /* @__PURE__ */ jsx(Popover.Popup, {
370
- className: cn("folio-default-popover-popup", className),
371
- ...props,
372
- children
373
- })
374
- }) });
375
- }
376
- function DefaultPopoverClose(props) {
377
- return /* @__PURE__ */ jsx(Popover.Close, { ...props });
378
- }
379
- const DefaultPopover = {
380
- Root: DefaultPopoverRoot,
381
- Trigger: DefaultPopoverTrigger,
382
- Popup: DefaultPopoverPopup,
383
- Close: DefaultPopoverClose
384
- };
385
- //#endregion
386
- //#region src/ui/defaults/select.tsx
387
- /**
388
- * Built-in, dependency-light Select parts used when a consumer does not inject
389
- * its own. Each part wraps the matching `@base-ui/react` primitive (real
390
- * listbox semantics, keyboard navigation, portalled positioning) with minimal
391
- * styling. The default popup omits the scroll arrows and item-aligned
392
- * positioning of a fully styled design-system Select; consumers inject their
393
- * own Select via `DocxEditor`'s `components` prop for that polish.
394
- */
395
- function DefaultSelectRoot(props) {
396
- return /* @__PURE__ */ jsx(Select.Root, { ...props });
397
- }
398
- function DefaultSelectTrigger({ className, children, size, ...props }) {
399
- return /* @__PURE__ */ jsxs(Select.Trigger, {
400
- className: cn("folio-default-select-trigger", className),
401
- "data-size": size,
402
- ...props,
403
- children: [children, /* @__PURE__ */ jsx(Select.Icon, {
404
- className: "folio-default-select-icon",
405
- children: /* @__PURE__ */ jsx("svg", {
406
- "aria-hidden": "true",
407
- fill: "none",
408
- height: "16",
409
- stroke: "currentColor",
410
- strokeLinecap: "round",
411
- strokeLinejoin: "round",
412
- strokeWidth: "2",
413
- viewBox: "0 0 24 24",
414
- width: "16",
415
- xmlns: "http://www.w3.org/2000/svg",
416
- children: /* @__PURE__ */ jsx("path", { d: "m7 15 5 5 5-5M7 9l5-5 5 5" })
417
- })
418
- })]
419
- });
420
- }
421
- function DefaultSelectValue({ className, ...props }) {
422
- return /* @__PURE__ */ jsx(Select.Value, {
423
- className: cn("folio-default-select-value", className),
424
- ...props
425
- });
426
- }
427
- function DefaultSelectPopup({ className, children, ...props }) {
428
- return /* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, {
429
- align: "start",
430
- className: "folio-default-select-positioner folio-root",
431
- side: "bottom",
432
- sideOffset: 4,
433
- children: /* @__PURE__ */ jsx(Select.Popup, {
434
- className: cn("folio-default-select-popup", className),
435
- ...props,
436
- children: /* @__PURE__ */ jsx(Select.List, { children })
437
- })
438
- }) });
439
- }
440
- function DefaultSelectItem({ className, children, ...props }) {
441
- return /* @__PURE__ */ jsx(Select.Item, {
442
- className: cn("folio-default-select-item", className),
443
- ...props,
444
- children: /* @__PURE__ */ jsx(Select.ItemText, { children })
445
- });
446
- }
447
- //#endregion
448
- //#region src/ui/folio-ui.tsx
449
- const DEFAULT_COMPONENTS = {
450
- Button: DefaultButton,
451
- Dialog: DefaultDialog,
452
- Select: {
453
- Root: DefaultSelectRoot,
454
- Trigger: DefaultSelectTrigger,
455
- Value: DefaultSelectValue,
456
- Popup: DefaultSelectPopup,
457
- Item: DefaultSelectItem
458
- },
459
- Menu: DefaultMenu,
460
- Popover: DefaultPopover,
461
- Input: DefaultInput,
462
- Checkbox: DefaultCheckbox,
463
- ColorPicker: DefaultColorPicker,
464
- DatePickerPopover: DefaultDatePickerPopover,
465
- OutlineRail: DefaultOutlineRail
466
- };
467
- const FolioUIContext = createContext(DEFAULT_COMPONENTS);
468
- /**
469
- * Merge a consumer's partial overrides over the built-in defaults: every key is
470
- * present (defaults fill the gaps), and each provided override wins. Pure, so
471
- * the resolution contract is unit-tested independently of React.
472
- */
473
- function resolveFolioComponents(components) {
474
- return components ? {
475
- ...DEFAULT_COMPONENTS,
476
- ...components
477
- } : DEFAULT_COMPONENTS;
478
- }
479
- function FolioUIProvider({ components, children }) {
480
- const value = resolveFolioComponents(components);
481
- return /* @__PURE__ */ jsx(FolioUIContext.Provider, {
482
- value,
483
- children
484
- });
485
- }
486
- function useFolioUI() {
487
- return useContext(FolioUIContext);
488
- }
489
- //#endregion
490
- export { cn as i, FolioUIProvider as n, useFolioUI as r, DEFAULT_COMPONENTS as t };