@lattice-php/lattice 0.14.0 → 0.16.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 (83) hide show
  1. package/dist/action/components/action-group.js +1 -1
  2. package/dist/action/components/action-group.js.map +1 -1
  3. package/dist/action/components/action-menu-context.d.ts +1 -1
  4. package/dist/action/components/action-menu-context.js +1 -1
  5. package/dist/action/components/action-menu-context.js.map +1 -1
  6. package/dist/core/components/card.js +4 -4
  7. package/dist/core/components/card.js.map +1 -1
  8. package/dist/core/components/chart.js +49 -2
  9. package/dist/core/components/chart.js.map +1 -1
  10. package/dist/core/components/collapsible.js +1 -0
  11. package/dist/core/components/collapsible.js.map +1 -1
  12. package/dist/core/components/control.d.ts +1 -1
  13. package/dist/core/components/dialog.d.ts +1 -1
  14. package/dist/core/components/dialog.js +2 -2
  15. package/dist/core/components/dialog.js.map +1 -1
  16. package/dist/core/components/floating-panel.js +2 -2
  17. package/dist/core/components/floating-panel.js.map +1 -1
  18. package/dist/core/components/grid.js +1 -0
  19. package/dist/core/components/grid.js.map +1 -1
  20. package/dist/core/components/link.js +61 -3
  21. package/dist/core/components/link.js.map +1 -1
  22. package/dist/core/components/popover.d.ts +1 -1
  23. package/dist/core/components/popover.js +1 -1
  24. package/dist/core/components/popover.js.map +1 -1
  25. package/dist/core/components/section.js +4 -3
  26. package/dist/core/components/section.js.map +1 -1
  27. package/dist/core/components/stack.js +1 -0
  28. package/dist/core/components/stack.js.map +1 -1
  29. package/dist/form/components/base/submit-button.js +1 -1
  30. package/dist/form/components/base/submit-button.js.map +1 -1
  31. package/dist/form/components/fields/date-picker-control.js +13 -15
  32. package/dist/form/components/fields/date-picker-control.js.map +1 -1
  33. package/dist/form/components/fields/rich-editor.js +1 -1
  34. package/dist/form/components/fields/rich-editor.js.map +1 -1
  35. package/dist/form/components/fields/time-input.js +54 -15
  36. package/dist/form/components/fields/time-input.js.map +1 -1
  37. package/dist/form/components/fields/time-picker-columns.d.ts +23 -0
  38. package/dist/form/components/fields/time-picker-columns.js +76 -0
  39. package/dist/form/components/fields/time-picker-columns.js.map +1 -0
  40. package/dist/form/components/fields/time-picker.d.ts +17 -0
  41. package/dist/form/components/fields/time-picker.js +132 -0
  42. package/dist/form/components/fields/time-picker.js.map +1 -0
  43. package/dist/form/components/form.js +2 -1
  44. package/dist/form/components/form.js.map +1 -1
  45. package/dist/format/number.d.ts +2 -0
  46. package/dist/format/number.js +23 -0
  47. package/dist/format/number.js.map +1 -0
  48. package/dist/{table/components/cells → format}/numeric.js +1 -1
  49. package/dist/format/numeric.js.map +1 -0
  50. package/dist/format/value.d.ts +6 -0
  51. package/dist/format/value.js +17 -0
  52. package/dist/format/value.js.map +1 -0
  53. package/dist/index.d.ts +1 -1
  54. package/dist/lattice.css +68 -36
  55. package/dist/layout/menu-item.js +1 -1
  56. package/dist/layout/menu-item.js.map +1 -1
  57. package/dist/layout/sidebar.js +2 -2
  58. package/dist/layout/sidebar.js.map +1 -1
  59. package/dist/layout/topbar.js +1 -1
  60. package/dist/layout/topbar.js.map +1 -1
  61. package/dist/table/components/cells/money-cell.js +11 -12
  62. package/dist/table/components/cells/money-cell.js.map +1 -1
  63. package/dist/table/components/cells/number-cell.js +11 -11
  64. package/dist/table/components/cells/number-cell.js.map +1 -1
  65. package/dist/table/components/column-filter-control.js +2 -2
  66. package/dist/table/components/column-filter-control.js.map +1 -1
  67. package/dist/table/components/filter-controls.js +2 -2
  68. package/dist/table/components/filter-controls.js.map +1 -1
  69. package/dist/table/components/pagination.js +7 -6
  70. package/dist/table/components/pagination.js.map +1 -1
  71. package/dist/table/components/table.js +9 -3
  72. package/dist/table/components/table.js.map +1 -1
  73. package/dist/table/format.d.ts +2 -0
  74. package/dist/table/format.js +2 -0
  75. package/dist/table/format.js.map +1 -1
  76. package/dist/toast/toaster.js +1 -1
  77. package/dist/toast/toaster.js.map +1 -1
  78. package/dist/types/generated.d.ts +21 -0
  79. package/dist/types/index.d.ts +1 -1
  80. package/package.json +1 -1
  81. package/resources/icons/clock.svg +16 -0
  82. package/dist/table/components/cells/numeric.js.map +0 -1
  83. /package/dist/{table/components/cells → format}/numeric.d.ts +0 -0
@@ -0,0 +1,132 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { buildTimeColumns } from "./time-picker-columns.js";
3
+ import { useEffect, useRef } from "react";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region resources/js/form/components/fields/time-picker.tsx
6
+ function TimePicker({ value, onChange, step, min, max, disabled = false, readOnly = false, labels, testId }) {
7
+ const containerRef = useRef(null);
8
+ const columns = buildTimeColumns(step, {
9
+ min,
10
+ max,
11
+ current: value
12
+ });
13
+ const current = value ?? {
14
+ hour: 0,
15
+ minute: 0,
16
+ second: 0
17
+ };
18
+ const interactive = !disabled && !readOnly;
19
+ const columnList = [
20
+ {
21
+ key: "hour",
22
+ label: labels?.hour ?? "Hour",
23
+ options: columns.hours,
24
+ selected: current.hour
25
+ },
26
+ {
27
+ key: "minute",
28
+ label: labels?.minute ?? "Minute",
29
+ options: columns.minutes,
30
+ selected: current.minute
31
+ },
32
+ ...columns.seconds ? [{
33
+ key: "second",
34
+ label: labels?.second ?? "Second",
35
+ options: columns.seconds,
36
+ selected: current.second
37
+ }] : []
38
+ ];
39
+ function focusColumn(index) {
40
+ const target = (containerRef.current?.querySelectorAll("[role=\"listbox\"]"))?.[index];
41
+ if (!target) return;
42
+ (target.querySelector("[data-active=\"true\"]") ?? target.querySelector("[role=\"option\"]"))?.focus();
43
+ }
44
+ return /* @__PURE__ */ jsx("div", {
45
+ ref: containerRef,
46
+ className: "flex gap-1",
47
+ "data-test": testId,
48
+ children: columnList.map((column, index) => /* @__PURE__ */ jsx(TimeColumn, {
49
+ label: column.label,
50
+ options: column.options,
51
+ selected: value ? column.selected : null,
52
+ disabled: !interactive,
53
+ onSelect: (optionValue) => interactive && onChange({
54
+ ...current,
55
+ [column.key]: optionValue
56
+ }),
57
+ onHorizontal: (direction) => focusColumn(index + direction)
58
+ }, column.key))
59
+ });
60
+ }
61
+ function TimeColumn({ label, options, selected, disabled, onSelect, onHorizontal }) {
62
+ const listRef = useRef(null);
63
+ const enabledValues = options.filter((option) => !option.disabled).map((option) => option.value);
64
+ const activeValue = selected ?? enabledValues[0] ?? options[0]?.value ?? 0;
65
+ useEffect(() => {
66
+ (listRef.current?.querySelector("[data-active=\"true\"]"))?.scrollIntoView?.({ block: "nearest" });
67
+ }, [selected]);
68
+ function moveTo(nextValue) {
69
+ if (nextValue == null) return;
70
+ listRef.current?.querySelector(`[data-value="${nextValue}"]`)?.focus();
71
+ onSelect(nextValue);
72
+ }
73
+ function handleKeyDown(event) {
74
+ if (disabled) return;
75
+ const index = enabledValues.indexOf(activeValue);
76
+ switch (event.key) {
77
+ case "ArrowDown":
78
+ event.preventDefault();
79
+ moveTo(enabledValues[Math.min(index + 1, enabledValues.length - 1)]);
80
+ break;
81
+ case "ArrowUp":
82
+ event.preventDefault();
83
+ moveTo(enabledValues[Math.max(index - 1, 0)]);
84
+ break;
85
+ case "Home":
86
+ event.preventDefault();
87
+ moveTo(enabledValues[0]);
88
+ break;
89
+ case "End":
90
+ event.preventDefault();
91
+ moveTo(enabledValues[enabledValues.length - 1]);
92
+ break;
93
+ case "ArrowRight":
94
+ event.preventDefault();
95
+ onHorizontal(1);
96
+ break;
97
+ case "ArrowLeft":
98
+ event.preventDefault();
99
+ onHorizontal(-1);
100
+ break;
101
+ }
102
+ }
103
+ return /* @__PURE__ */ jsx("div", {
104
+ ref: listRef,
105
+ role: "listbox",
106
+ "aria-label": label,
107
+ "aria-orientation": "vertical",
108
+ tabIndex: -1,
109
+ className: "flex max-h-40 w-14 flex-col overflow-y-auto",
110
+ onKeyDown: handleKeyDown,
111
+ children: options.map((option) => {
112
+ const isSelected = selected === option.value;
113
+ return /* @__PURE__ */ jsx("button", {
114
+ type: "button",
115
+ role: "option",
116
+ "aria-selected": isSelected,
117
+ "aria-label": `${label} ${option.label}`,
118
+ "data-value": option.value,
119
+ "data-active": activeValue === option.value,
120
+ disabled: disabled || option.disabled,
121
+ tabIndex: activeValue === option.value ? 0 : -1,
122
+ onClick: () => onSelect(option.value),
123
+ className: cn("shrink-0 rounded-lt-sm px-2 py-1 text-sm text-lt-fg hover:bg-lt-muted", isSelected && "bg-lt-primary text-lt-primary-fg", (disabled || option.disabled) && "cursor-not-allowed opacity-40"),
124
+ children: option.label
125
+ }, option.value);
126
+ })
127
+ });
128
+ }
129
+ //#endregion
130
+ export { TimePicker };
131
+
132
+ //# sourceMappingURL=time-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time-picker.js","names":[],"sources":["../../../../resources/js/form/components/fields/time-picker.tsx"],"sourcesContent":["import { type KeyboardEvent, useEffect, useRef } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { buildTimeColumns, type TimeColumnOption, type TimeValue } from \"./time-picker-columns\";\n\nexport type TimePickerProps = {\n value: TimeValue | null;\n onChange: (next: TimeValue) => void;\n step?: number | null;\n min?: string | null;\n max?: string | null;\n disabled?: boolean;\n readOnly?: boolean;\n labels?: { hour?: string; minute?: string; second?: string };\n testId?: string;\n};\n\nexport function TimePicker({\n value,\n onChange,\n step,\n min,\n max,\n disabled = false,\n readOnly = false,\n labels,\n testId,\n}: TimePickerProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const columns = buildTimeColumns(step, { min, max, current: value });\n const current: TimeValue = value ?? { hour: 0, minute: 0, second: 0 };\n const interactive = !disabled && !readOnly;\n\n const columnList = [\n {\n key: \"hour\" as const,\n label: labels?.hour ?? \"Hour\",\n options: columns.hours,\n selected: current.hour,\n },\n {\n key: \"minute\" as const,\n label: labels?.minute ?? \"Minute\",\n options: columns.minutes,\n selected: current.minute,\n },\n ...(columns.seconds\n ? [\n {\n key: \"second\" as const,\n label: labels?.second ?? \"Second\",\n options: columns.seconds,\n selected: current.second,\n },\n ]\n : []),\n ];\n\n function focusColumn(index: number) {\n const listboxes = containerRef.current?.querySelectorAll<HTMLElement>('[role=\"listbox\"]');\n const target = listboxes?.[index];\n\n if (!target) {\n return;\n }\n\n const active =\n target.querySelector<HTMLElement>('[data-active=\"true\"]') ??\n target.querySelector<HTMLElement>('[role=\"option\"]');\n\n active?.focus();\n }\n\n return (\n <div ref={containerRef} className=\"flex gap-1\" data-test={testId}>\n {columnList.map((column, index) => (\n <TimeColumn\n key={column.key}\n label={column.label}\n options={column.options}\n selected={value ? column.selected : null}\n disabled={!interactive}\n onSelect={(optionValue) =>\n interactive && onChange({ ...current, [column.key]: optionValue })\n }\n onHorizontal={(direction) => focusColumn(index + direction)}\n />\n ))}\n </div>\n );\n}\n\nfunction TimeColumn({\n label,\n options,\n selected,\n disabled,\n onSelect,\n onHorizontal,\n}: {\n label: string;\n options: TimeColumnOption[];\n selected: number | null;\n disabled: boolean;\n onSelect: (value: number) => void;\n onHorizontal: (direction: 1 | -1) => void;\n}) {\n const listRef = useRef<HTMLDivElement>(null);\n const enabledValues = options.filter((option) => !option.disabled).map((option) => option.value);\n const activeValue = selected ?? enabledValues[0] ?? options[0]?.value ?? 0;\n\n useEffect(() => {\n const active = listRef.current?.querySelector<HTMLElement>('[data-active=\"true\"]');\n\n active?.scrollIntoView?.({ block: \"nearest\" });\n }, [selected]);\n\n function moveTo(nextValue: number | undefined) {\n if (nextValue == null) {\n return;\n }\n\n listRef.current?.querySelector<HTMLElement>(`[data-value=\"${nextValue}\"]`)?.focus();\n onSelect(nextValue);\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (disabled) {\n return;\n }\n\n const index = enabledValues.indexOf(activeValue);\n\n switch (event.key) {\n case \"ArrowDown\":\n event.preventDefault();\n moveTo(enabledValues[Math.min(index + 1, enabledValues.length - 1)]);\n break;\n case \"ArrowUp\":\n event.preventDefault();\n moveTo(enabledValues[Math.max(index - 1, 0)]);\n break;\n case \"Home\":\n event.preventDefault();\n moveTo(enabledValues[0]);\n break;\n case \"End\":\n event.preventDefault();\n moveTo(enabledValues[enabledValues.length - 1]);\n break;\n case \"ArrowRight\":\n event.preventDefault();\n onHorizontal(1);\n break;\n case \"ArrowLeft\":\n event.preventDefault();\n onHorizontal(-1);\n break;\n }\n }\n\n return (\n <div\n ref={listRef}\n role=\"listbox\"\n aria-label={label}\n aria-orientation=\"vertical\"\n tabIndex={-1}\n className=\"flex max-h-40 w-14 flex-col overflow-y-auto\"\n onKeyDown={handleKeyDown}\n >\n {options.map((option) => {\n const isSelected = selected === option.value;\n\n return (\n <button\n key={option.value}\n type=\"button\"\n role=\"option\"\n aria-selected={isSelected}\n aria-label={`${label} ${option.label}`}\n data-value={option.value}\n data-active={activeValue === option.value}\n disabled={disabled || option.disabled}\n tabIndex={activeValue === option.value ? 0 : -1}\n onClick={() => onSelect(option.value)}\n className={cn(\n \"shrink-0 rounded-lt-sm px-2 py-1 text-sm text-lt-fg hover:bg-lt-muted\",\n isSelected && \"bg-lt-primary text-lt-primary-fg\",\n (disabled || option.disabled) && \"cursor-not-allowed opacity-40\",\n )}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -43,7 +43,7 @@ function FormBody({ action, children, componentRef, nodes, shouldRenderSubmitBut
43
43
  children: /* @__PURE__ */ jsxs("div", {
44
44
  className: "flex flex-col gap-6",
45
45
  children: [children, shouldRenderSubmitButton && /* @__PURE__ */ jsx("div", {
46
- className: "flex justify-end rounded-lt border border-lt-border bg-lt-surface px-6 py-4 shadow-lt-sm",
46
+ className: "flex justify-end rounded-lt border border-lt-border bg-lt-surface px-lt-gutter py-4 shadow-lt-sm",
47
47
  children: /* @__PURE__ */ jsx(FormSubmitButton, {
48
48
  label: submitLabel,
49
49
  summaryLabel
@@ -74,6 +74,7 @@ var FormComponent = ({ children, node }) => {
74
74
  const validationTimeout = props.validationTimeout ?? void 0;
75
75
  return /* @__PURE__ */ jsx(Form, {
76
76
  action,
77
+ "data-slot": "form",
77
78
  "data-lattice-component": node.id,
78
79
  errorBag,
79
80
  method,
@@ -1 +1 @@
1
- {"version":3,"file":"form.js","names":[],"sources":["../../../resources/js/form/components/form.tsx"],"sourcesContent":["import { Form as InertiaForm } from \"@inertiajs/react\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport type { Node, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { useEffect, useMemo } from \"react\";\nimport { FormSubmitButton } from \"./base/submit-button\";\nimport { FormProvider } from \"./context\";\nimport { walkFields } from \"./field-props\";\nimport { PrefillProvider } from \"./prefill-context\";\nimport { ResolvedNodesProvider } from \"./resolved-nodes\";\nimport { useFormResolver } from \"./use-form-resolver\";\nimport { FormValuesProvider } from \"./values\";\n\ntype CollectedFields = {\n labels: Record<string, string>;\n values: Record<string, unknown>;\n};\n\nfunction collectFields(nodes: Node[] | undefined): CollectedFields {\n const collected: CollectedFields = { labels: {}, values: {} };\n\n walkFields(nodes, (props) => {\n if (!props.name) {\n return;\n }\n if (props.label) {\n collected.labels[props.name] = props.label;\n }\n if (props.value !== undefined) {\n collected.values[props.name] = props.value;\n }\n });\n\n return collected;\n}\n\nfunction FormResetListener({\n componentId,\n reset,\n}: {\n componentId?: string;\n reset: (...fields: string[]) => void;\n}) {\n useEffect(() => {\n const handler = (event: Event) => {\n const detail = (event as CustomEvent<{ form: string | null }>).detail;\n\n if (!detail?.form || detail.form === componentId) {\n reset();\n }\n };\n\n window.addEventListener(LATTICE_EVENT.resetForm, handler);\n\n return () => window.removeEventListener(LATTICE_EVENT.resetForm, handler);\n }, [componentId, reset]);\n\n return null;\n}\n\nfunction FormBody({\n action,\n children,\n componentRef,\n nodes,\n shouldRenderSubmitButton,\n submitLabel,\n summaryLabel,\n}: {\n action: string;\n children: React.ReactNode;\n componentRef: string;\n nodes: Node[] | undefined;\n shouldRenderSubmitButton: boolean;\n submitLabel: string;\n summaryLabel: string;\n}) {\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(action, componentRef, nodes);\n\n return (\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <div className=\"flex flex-col gap-6\">\n {children}\n\n {shouldRenderSubmitButton && (\n <div className=\"flex justify-end rounded-lt border border-lt-border bg-lt-surface px-6 py-4 shadow-lt-sm\">\n <FormSubmitButton label={submitLabel} summaryLabel={summaryLabel} />\n </div>\n )}\n </div>\n </ResolvedNodesProvider>\n </PrefillProvider>\n );\n}\n\nexport const FormComponent: RendererComponent<\"form\"> = ({ children, node }) => {\n const props = node.props;\n const action = props.action ?? \"#\";\n const errorBag = props.errorBag;\n const componentRef = props.ref ?? \"\";\n const method = props.method ?? \"post\";\n const precognitive = props.precognitive;\n const resetOnError = props.resetOnError ?? false;\n const resetOnSuccess = props.resetOnSuccess ?? [];\n const state = props.state;\n const { labels: fieldLabels, values: fieldValues } = useMemo(\n () => collectFields(node.schema),\n [node.schema],\n );\n const initialValues = useMemo(() => ({ ...fieldValues, ...state }), [fieldValues, state]);\n const shouldRenderSubmitButton = props.submitButton;\n const submitLabel = props.submitLabel ?? \"Submit\";\n const summaryLabel = props.validationSummaryLabel;\n const validationTimeout = props.validationTimeout ?? undefined;\n\n return (\n <InertiaForm\n action={action}\n data-lattice-component={node.id}\n errorBag={errorBag}\n method={method}\n resetOnError={resetOnError}\n resetOnSuccess={resetOnSuccess}\n validationTimeout={precognitive ? validationTimeout : undefined}\n headers={withHeaders(componentRef)}\n className=\"mx-auto flex w-full max-w-2xl flex-col gap-6\"\n >\n {({ clearErrors, errors, processing, reset, validate }) => (\n <FormProvider\n value={{\n action,\n clearErrors: (field) => clearErrors(field),\n componentId: node.id,\n componentRef,\n errors: errors as Record<string, string | undefined>,\n fieldLabels,\n precognitive,\n processing,\n validate: (field) => validate(field),\n }}\n >\n <FormResetListener componentId={node.id} reset={reset} />\n\n {props.status && (\n <div className=\"text-center text-sm font-medium text-lt-success\">{props.status}</div>\n )}\n\n <FormValuesProvider initial={initialValues}>\n <FormBody\n action={action}\n componentRef={componentRef}\n nodes={node.schema}\n shouldRenderSubmitButton={shouldRenderSubmitButton}\n submitLabel={submitLabel}\n summaryLabel={summaryLabel}\n >\n {children}\n </FormBody>\n </FormValuesProvider>\n </FormProvider>\n )}\n </InertiaForm>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAkBA,SAAS,cAAc,OAA4C;CACjE,MAAM,YAA6B;EAAE,QAAQ,CAAC;EAAG,QAAQ,CAAC;CAAE;CAE5D,WAAW,QAAQ,UAAU;EAC3B,IAAI,CAAC,MAAM,MACT;EAEF,IAAI,MAAM,OACR,UAAU,OAAO,MAAM,QAAQ,MAAM;EAEvC,IAAI,MAAM,UAAU,KAAA,GAClB,UAAU,OAAO,MAAM,QAAQ,MAAM;CAEzC,CAAC;CAED,OAAO;AACT;AAEA,SAAS,kBAAkB,EACzB,aACA,SAIC;CACD,gBAAgB;EACd,MAAM,WAAW,UAAiB;GAChC,MAAM,SAAU,MAA+C;GAE/D,IAAI,CAAC,QAAQ,QAAQ,OAAO,SAAS,aACnC,MAAM;EAEV;EAEA,OAAO,iBAAiB,cAAc,WAAW,OAAO;EAExD,aAAa,OAAO,oBAAoB,cAAc,WAAW,OAAO;CAC1E,GAAG,CAAC,aAAa,KAAK,CAAC;CAEvB,OAAO;AACT;AAEA,SAAS,SAAS,EAChB,QACA,UACA,cACA,OACA,0BACA,aACA,gBASC;CACD,MAAM,EAAE,OAAO,eAAe,iBAAiB,gBAAgB,QAAQ,cAAc,KAAK;CAE1F,OACE,oBAAC,iBAAD;EAAiB,OAAO,EAAE,aAAa;YACrC,oBAAC,uBAAD;GAAuB,OAAO;aAC5B,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,UAEA,4BACC,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,kBAAD;MAAkB,OAAO;MAA2B;KAAe,CAAA;IAChE,CAAA,CAEJ;;EACgB,CAAA;CACR,CAAA;AAErB;AAEA,IAAa,iBAA4C,EAAE,UAAU,WAAW;CAC9E,MAAM,QAAQ,KAAK;CACnB,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,WAAW,MAAM;CACvB,MAAM,eAAe,MAAM,OAAO;CAClC,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,eAAe,MAAM;CAC3B,MAAM,eAAe,MAAM,gBAAgB;CAC3C,MAAM,iBAAiB,MAAM,kBAAkB,CAAC;CAChD,MAAM,QAAQ,MAAM;CACpB,MAAM,EAAE,QAAQ,aAAa,QAAQ,gBAAgB,cAC7C,cAAc,KAAK,MAAM,GAC/B,CAAC,KAAK,MAAM,CACd;CACA,MAAM,gBAAgB,eAAe;EAAE,GAAG;EAAa,GAAG;CAAM,IAAI,CAAC,aAAa,KAAK,CAAC;CACxF,MAAM,2BAA2B,MAAM;CACvC,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,eAAe,MAAM;CAC3B,MAAM,oBAAoB,MAAM,qBAAqB,KAAA;CAErD,OACE,oBAAC,MAAD;EACU;EACR,0BAAwB,KAAK;EACnB;EACF;EACM;EACE;EAChB,mBAAmB,eAAe,oBAAoB,KAAA;EACtD,SAAS,YAAY,YAAY;EACjC,WAAU;aAER,EAAE,aAAa,QAAQ,YAAY,OAAO,eAC1C,qBAAC,cAAD;GACE,OAAO;IACL;IACA,cAAc,UAAU,YAAY,KAAK;IACzC,aAAa,KAAK;IAClB;IACQ;IACR;IACA;IACA;IACA,WAAW,UAAU,SAAS,KAAK;GACrC;aAXF;IAaE,oBAAC,mBAAD;KAAmB,aAAa,KAAK;KAAW;IAAQ,CAAA;IAEvD,MAAM,UACL,oBAAC,OAAD;KAAK,WAAU;eAAmD,MAAM;IAAY,CAAA;IAGtF,oBAAC,oBAAD;KAAoB,SAAS;eAC3B,oBAAC,UAAD;MACU;MACM;MACd,OAAO,KAAK;MACc;MACb;MACC;MAEb;KACO,CAAA;IACQ,CAAA;GACR;;CAEL,CAAA;AAEjB"}
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../../resources/js/form/components/form.tsx"],"sourcesContent":["import { Form as InertiaForm } from \"@inertiajs/react\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport type { Node, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { useEffect, useMemo } from \"react\";\nimport { FormSubmitButton } from \"./base/submit-button\";\nimport { FormProvider } from \"./context\";\nimport { walkFields } from \"./field-props\";\nimport { PrefillProvider } from \"./prefill-context\";\nimport { ResolvedNodesProvider } from \"./resolved-nodes\";\nimport { useFormResolver } from \"./use-form-resolver\";\nimport { FormValuesProvider } from \"./values\";\n\ntype CollectedFields = {\n labels: Record<string, string>;\n values: Record<string, unknown>;\n};\n\nfunction collectFields(nodes: Node[] | undefined): CollectedFields {\n const collected: CollectedFields = { labels: {}, values: {} };\n\n walkFields(nodes, (props) => {\n if (!props.name) {\n return;\n }\n if (props.label) {\n collected.labels[props.name] = props.label;\n }\n if (props.value !== undefined) {\n collected.values[props.name] = props.value;\n }\n });\n\n return collected;\n}\n\nfunction FormResetListener({\n componentId,\n reset,\n}: {\n componentId?: string;\n reset: (...fields: string[]) => void;\n}) {\n useEffect(() => {\n const handler = (event: Event) => {\n const detail = (event as CustomEvent<{ form: string | null }>).detail;\n\n if (!detail?.form || detail.form === componentId) {\n reset();\n }\n };\n\n window.addEventListener(LATTICE_EVENT.resetForm, handler);\n\n return () => window.removeEventListener(LATTICE_EVENT.resetForm, handler);\n }, [componentId, reset]);\n\n return null;\n}\n\nfunction FormBody({\n action,\n children,\n componentRef,\n nodes,\n shouldRenderSubmitButton,\n submitLabel,\n summaryLabel,\n}: {\n action: string;\n children: React.ReactNode;\n componentRef: string;\n nodes: Node[] | undefined;\n shouldRenderSubmitButton: boolean;\n submitLabel: string;\n summaryLabel: string;\n}) {\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(action, componentRef, nodes);\n\n return (\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <div className=\"flex flex-col gap-6\">\n {children}\n\n {shouldRenderSubmitButton && (\n <div className=\"flex justify-end rounded-lt border border-lt-border bg-lt-surface px-lt-gutter py-4 shadow-lt-sm\">\n <FormSubmitButton label={submitLabel} summaryLabel={summaryLabel} />\n </div>\n )}\n </div>\n </ResolvedNodesProvider>\n </PrefillProvider>\n );\n}\n\nexport const FormComponent: RendererComponent<\"form\"> = ({ children, node }) => {\n const props = node.props;\n const action = props.action ?? \"#\";\n const errorBag = props.errorBag;\n const componentRef = props.ref ?? \"\";\n const method = props.method ?? \"post\";\n const precognitive = props.precognitive;\n const resetOnError = props.resetOnError ?? false;\n const resetOnSuccess = props.resetOnSuccess ?? [];\n const state = props.state;\n const { labels: fieldLabels, values: fieldValues } = useMemo(\n () => collectFields(node.schema),\n [node.schema],\n );\n const initialValues = useMemo(() => ({ ...fieldValues, ...state }), [fieldValues, state]);\n const shouldRenderSubmitButton = props.submitButton;\n const submitLabel = props.submitLabel ?? \"Submit\";\n const summaryLabel = props.validationSummaryLabel;\n const validationTimeout = props.validationTimeout ?? undefined;\n\n return (\n <InertiaForm\n action={action}\n data-slot=\"form\"\n data-lattice-component={node.id}\n errorBag={errorBag}\n method={method}\n resetOnError={resetOnError}\n resetOnSuccess={resetOnSuccess}\n validationTimeout={precognitive ? validationTimeout : undefined}\n headers={withHeaders(componentRef)}\n className=\"mx-auto flex w-full max-w-2xl flex-col gap-6\"\n >\n {({ clearErrors, errors, processing, reset, validate }) => (\n <FormProvider\n value={{\n action,\n clearErrors: (field) => clearErrors(field),\n componentId: node.id,\n componentRef,\n errors: errors as Record<string, string | undefined>,\n fieldLabels,\n precognitive,\n processing,\n validate: (field) => validate(field),\n }}\n >\n <FormResetListener componentId={node.id} reset={reset} />\n\n {props.status && (\n <div className=\"text-center text-sm font-medium text-lt-success\">{props.status}</div>\n )}\n\n <FormValuesProvider initial={initialValues}>\n <FormBody\n action={action}\n componentRef={componentRef}\n nodes={node.schema}\n shouldRenderSubmitButton={shouldRenderSubmitButton}\n submitLabel={submitLabel}\n summaryLabel={summaryLabel}\n >\n {children}\n </FormBody>\n </FormValuesProvider>\n </FormProvider>\n )}\n </InertiaForm>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAkBA,SAAS,cAAc,OAA4C;CACjE,MAAM,YAA6B;EAAE,QAAQ,CAAC;EAAG,QAAQ,CAAC;CAAE;CAE5D,WAAW,QAAQ,UAAU;EAC3B,IAAI,CAAC,MAAM,MACT;EAEF,IAAI,MAAM,OACR,UAAU,OAAO,MAAM,QAAQ,MAAM;EAEvC,IAAI,MAAM,UAAU,KAAA,GAClB,UAAU,OAAO,MAAM,QAAQ,MAAM;CAEzC,CAAC;CAED,OAAO;AACT;AAEA,SAAS,kBAAkB,EACzB,aACA,SAIC;CACD,gBAAgB;EACd,MAAM,WAAW,UAAiB;GAChC,MAAM,SAAU,MAA+C;GAE/D,IAAI,CAAC,QAAQ,QAAQ,OAAO,SAAS,aACnC,MAAM;EAEV;EAEA,OAAO,iBAAiB,cAAc,WAAW,OAAO;EAExD,aAAa,OAAO,oBAAoB,cAAc,WAAW,OAAO;CAC1E,GAAG,CAAC,aAAa,KAAK,CAAC;CAEvB,OAAO;AACT;AAEA,SAAS,SAAS,EAChB,QACA,UACA,cACA,OACA,0BACA,aACA,gBASC;CACD,MAAM,EAAE,OAAO,eAAe,iBAAiB,gBAAgB,QAAQ,cAAc,KAAK;CAE1F,OACE,oBAAC,iBAAD;EAAiB,OAAO,EAAE,aAAa;YACrC,oBAAC,uBAAD;GAAuB,OAAO;aAC5B,qBAAC,OAAD;IAAK,WAAU;cAAf,CACG,UAEA,4BACC,oBAAC,OAAD;KAAK,WAAU;eACb,oBAAC,kBAAD;MAAkB,OAAO;MAA2B;KAAe,CAAA;IAChE,CAAA,CAEJ;;EACgB,CAAA;CACR,CAAA;AAErB;AAEA,IAAa,iBAA4C,EAAE,UAAU,WAAW;CAC9E,MAAM,QAAQ,KAAK;CACnB,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,WAAW,MAAM;CACvB,MAAM,eAAe,MAAM,OAAO;CAClC,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,eAAe,MAAM;CAC3B,MAAM,eAAe,MAAM,gBAAgB;CAC3C,MAAM,iBAAiB,MAAM,kBAAkB,CAAC;CAChD,MAAM,QAAQ,MAAM;CACpB,MAAM,EAAE,QAAQ,aAAa,QAAQ,gBAAgB,cAC7C,cAAc,KAAK,MAAM,GAC/B,CAAC,KAAK,MAAM,CACd;CACA,MAAM,gBAAgB,eAAe;EAAE,GAAG;EAAa,GAAG;CAAM,IAAI,CAAC,aAAa,KAAK,CAAC;CACxF,MAAM,2BAA2B,MAAM;CACvC,MAAM,cAAc,MAAM,eAAe;CACzC,MAAM,eAAe,MAAM;CAC3B,MAAM,oBAAoB,MAAM,qBAAqB,KAAA;CAErD,OACE,oBAAC,MAAD;EACU;EACR,aAAU;EACV,0BAAwB,KAAK;EACnB;EACF;EACM;EACE;EAChB,mBAAmB,eAAe,oBAAoB,KAAA;EACtD,SAAS,YAAY,YAAY;EACjC,WAAU;aAER,EAAE,aAAa,QAAQ,YAAY,OAAO,eAC1C,qBAAC,cAAD;GACE,OAAO;IACL;IACA,cAAc,UAAU,YAAY,KAAK;IACzC,aAAa,KAAK;IAClB;IACQ;IACR;IACA;IACA;IACA,WAAW,UAAU,SAAS,KAAK;GACrC;aAXF;IAaE,oBAAC,mBAAD;KAAmB,aAAa,KAAK;KAAW;IAAQ,CAAA;IAEvD,MAAM,UACL,oBAAC,OAAD;KAAK,WAAU;eAAmD,MAAM;IAAY,CAAA;IAGtF,oBAAC,oBAAD;KAAoB,SAAS;eAC3B,oBAAC,UAAD;MACU;MACM;MACd,OAAO,KAAK;MACc;MACb;MACC;MAEb;KACO,CAAA;IACQ,CAAA;GACR;;CAEL,CAAA;AAEjB"}
@@ -0,0 +1,2 @@
1
+ import { NumberFormat } from '../types';
2
+ export declare function formatNumber(value: unknown, format: NumberFormat, locale: string): string;
@@ -0,0 +1,23 @@
1
+ import { numericValue } from "./numeric.js";
2
+ //#region resources/js/format/number.ts
3
+ function formatNumber(value, format, locale) {
4
+ const number = numericValue(value);
5
+ if (number === null) return String(value ?? "");
6
+ const options = {
7
+ notation: format.notation,
8
+ minimumFractionDigits: format.minimumFractionDigits ?? void 0,
9
+ maximumFractionDigits: format.maximumFractionDigits ?? void 0
10
+ };
11
+ if (format.currency) {
12
+ options.style = "currency";
13
+ options.currency = format.currency;
14
+ } else if (format.unit) {
15
+ options.style = "unit";
16
+ options.unit = format.unit;
17
+ }
18
+ return new Intl.NumberFormat(locale, options).format(number);
19
+ }
20
+ //#endregion
21
+ export { formatNumber };
22
+
23
+ //# sourceMappingURL=number.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"number.js","names":[],"sources":["../../resources/js/format/number.ts"],"sourcesContent":["import type { NumberFormat } from \"@lattice-php/lattice/types\";\nimport { numericValue } from \"./numeric\";\n\nexport function formatNumber(value: unknown, format: NumberFormat, locale: string): string {\n const number = numericValue(value);\n\n if (number === null) {\n return String(value ?? \"\");\n }\n\n const options: Intl.NumberFormatOptions = {\n notation: format.notation as Intl.NumberFormatOptions[\"notation\"],\n minimumFractionDigits: format.minimumFractionDigits ?? undefined,\n maximumFractionDigits: format.maximumFractionDigits ?? undefined,\n };\n\n if (format.currency) {\n options.style = \"currency\";\n options.currency = format.currency;\n } else if (format.unit) {\n options.style = \"unit\";\n options.unit = format.unit;\n }\n\n return new Intl.NumberFormat(locale, options).format(number);\n}\n"],"mappings":";;AAGA,SAAgB,aAAa,OAAgB,QAAsB,QAAwB;CACzF,MAAM,SAAS,aAAa,KAAK;CAEjC,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,MAAM,UAAoC;EACxC,UAAU,OAAO;EACjB,uBAAuB,OAAO,yBAAyB,KAAA;EACvD,uBAAuB,OAAO,yBAAyB,KAAA;CACzD;CAEA,IAAI,OAAO,UAAU;EACnB,QAAQ,QAAQ;EAChB,QAAQ,WAAW,OAAO;CAC5B,OAAO,IAAI,OAAO,MAAM;EACtB,QAAQ,QAAQ;EAChB,QAAQ,OAAO,OAAO;CACxB;CAEA,OAAO,IAAI,KAAK,aAAa,QAAQ,OAAO,EAAE,OAAO,MAAM;AAC7D"}
@@ -1,4 +1,4 @@
1
- //#region resources/js/table/components/cells/numeric.ts
1
+ //#region resources/js/format/numeric.ts
2
2
  function numericValue(value) {
3
3
  const number = typeof value === "number" ? value : Number(value);
4
4
  return value !== null && value !== void 0 && value !== "" && !Number.isNaN(number) ? number : null;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"numeric.js","names":[],"sources":["../../resources/js/format/numeric.ts"],"sourcesContent":["export function numericValue(value: unknown): number | null {\n const number = typeof value === \"number\" ? value : Number(value);\n\n return value !== null && value !== undefined && value !== \"\" && !Number.isNaN(number)\n ? number\n : null;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAE/D,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MAAM,CAAC,OAAO,MAAM,MAAM,IAChF,SACA;AACN"}
@@ -0,0 +1,6 @@
1
+ import { DateFormat, NumberFormat } from '../types';
2
+ export type Format = NumberFormat | DateFormat;
3
+ export declare function formatValue(value: unknown, format: Format | null, ctx: {
4
+ locale: string;
5
+ timezone: string;
6
+ }): string;
@@ -0,0 +1,17 @@
1
+ import { formatDateValue } from "../table/format.js";
2
+ import { formatNumber } from "./number.js";
3
+ //#region resources/js/format/value.ts
4
+ function isDateFormat(format) {
5
+ return format.kind === "date";
6
+ }
7
+ function formatValue(value, format, ctx) {
8
+ if (format === null) return String(value ?? "");
9
+ return isDateFormat(format) ? formatDateValue(value, format, {
10
+ locale: ctx.locale,
11
+ timeZone: ctx.timezone
12
+ }) : formatNumber(value, format, ctx.locale);
13
+ }
14
+ //#endregion
15
+ export { formatValue };
16
+
17
+ //# sourceMappingURL=value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value.js","names":[],"sources":["../../resources/js/format/value.ts"],"sourcesContent":["import type { DateFormat, NumberFormat } from \"@lattice-php/lattice/types\";\nimport { formatDateValue } from \"../table/format\";\nimport { formatNumber } from \"./number\";\n\nexport type Format = NumberFormat | DateFormat;\n\nfunction isDateFormat(format: Format): format is DateFormat {\n return format.kind === \"date\";\n}\n\nexport function formatValue(\n value: unknown,\n format: Format | null,\n ctx: { locale: string; timezone: string },\n): string {\n if (format === null) {\n return String(value ?? \"\");\n }\n\n return isDateFormat(format)\n ? formatDateValue(value, format, { locale: ctx.locale, timeZone: ctx.timezone })\n : formatNumber(value, format, ctx.locale);\n}\n"],"mappings":";;;AAMA,SAAS,aAAa,QAAsC;CAC1D,OAAO,OAAO,SAAS;AACzB;AAEA,SAAgB,YACd,OACA,QACA,KACQ;CACR,IAAI,WAAW,MACb,OAAO,OAAO,SAAS,EAAE;CAG3B,OAAO,aAAa,MAAM,IACtB,gBAAgB,OAAO,QAAQ;EAAE,QAAQ,IAAI;EAAQ,UAAU,IAAI;CAAS,CAAC,IAC7E,aAAa,OAAO,QAAQ,IAAI,MAAM;AAC5C"}
package/dist/index.d.ts CHANGED
@@ -25,7 +25,7 @@ export type { ButtonVariant } from './core/components/button';
25
25
  export type { ReloadComponentEvent } from './events/event-names';
26
26
  export type { ComponentProps, KnownPageContainer, LayoutPayload, Node, NodeOfType, NodeProps, NodeType, PageContainer, PageBreadcrumb, PagePayload, PropsOf, RendererComponent, RendererComponentModule, RendererComponentProps, Schema, UnknownComponent, WireNode, } from './core/types';
27
27
  export { RealtimeListeners } from './realtime/listeners';
28
- export type { ChannelVisibility, ListenerPayload } from './types/generated';
28
+ export type { ChannelVisibility, DateFormat, ListenerPayload, NumberFormat, } from './types/generated';
29
29
  export { columnCell } from './table/registry';
30
30
  export type { ColumnCellArgs, ColumnCellComponent, ColumnRegistry } from './table/registry';
31
31
  export type { ColumnProps, ColumnPropsOf } from './table/types';
package/dist/lattice.css CHANGED
@@ -61,15 +61,15 @@
61
61
  --lt-control-h-md: 2.25rem;
62
62
  --lt-control-h-lg: 2.5rem;
63
63
 
64
+ /* Table cell padding — table density lever */
65
+ --lt-table-cell-x: calc(var(--spacing) * 4); /* 1rem */
66
+ --lt-table-cell-y: calc(var(--spacing) * 4); /* 1rem */
67
+
64
68
  --lt-font-weight-normal: 400;
65
69
  --lt-font-weight-medium: 500;
66
70
  --lt-font-weight-semibold: 600;
67
71
  --lt-font-weight-bold: 700;
68
72
 
69
- /* Focus-ring size (colour: --lt-ring) */
70
- --lt-ring-width: 0.125rem;
71
- --lt-ring-offset: 0.125rem;
72
-
73
73
  /* Elevation */
74
74
  --lt-shadow-xs: 0 1px 1px 0 oklch(0 0 0 / 0.04);
75
75
  --lt-shadow-sm: 0 1px 2px 0 oklch(0 0 0 / 0.05);
@@ -89,14 +89,15 @@
89
89
  /* Motion */
90
90
  --lt-duration-fast: 120ms;
91
91
  --lt-duration-base: 160ms;
92
- --lt-duration-slow: 240ms;
93
- --lt-ease: cubic-bezier(0.2, 0, 0, 1);
94
92
 
95
93
  /* Layout dimensions */
96
94
  --lt-sidebar-w: 16rem;
97
95
  --lt-sidebar-w-collapsed: 4rem;
98
96
  --lt-container-max: 80rem;
99
97
 
98
+ /* Card / section content inset — the gutter apps can break out of */
99
+ --lt-gutter: calc(var(--spacing) * 6); /* 1.5rem */
100
+
100
101
  color-scheme: light;
101
102
  }
102
103
 
@@ -201,6 +202,13 @@
201
202
  --spacing-lt-control-sm: var(--lt-control-h-sm);
202
203
  --spacing-lt-control-md: var(--lt-control-h-md);
203
204
  --spacing-lt-control-lg: var(--lt-control-h-lg);
205
+ --spacing-lt-gutter: var(--lt-gutter);
206
+ --spacing-lt-cell-x: var(--lt-table-cell-x);
207
+ --spacing-lt-cell-y: var(--lt-table-cell-y);
208
+ --font-weight-lt-normal: var(--lt-font-weight-normal);
209
+ --font-weight-lt-medium: var(--lt-font-weight-medium);
210
+ --font-weight-lt-semibold: var(--lt-font-weight-semibold);
211
+ --font-weight-lt-bold: var(--lt-font-weight-bold);
204
212
  --animate-lt-toast-in: lt-toast-in var(--lt-duration-base) ease-out;
205
213
  --animate-lt-toast-out: lt-toast-out var(--lt-duration-fast) ease-in;
206
214
  }
@@ -238,6 +246,30 @@
238
246
  --text-7xl--line-height: 1;
239
247
  }
240
248
 
249
+ /*
250
+ * Layering utilities backed by the --lt-z-* scale. Tailwind v4 does not expose
251
+ * z-index as a themeable namespace, so these are defined as named utilities
252
+ * (the scale itself stays the single source of truth in the :root block above).
253
+ */
254
+ @utility z-lt-dropdown {
255
+ z-index: var(--lt-z-dropdown);
256
+ }
257
+ @utility z-lt-sticky {
258
+ z-index: var(--lt-z-sticky);
259
+ }
260
+ @utility z-lt-overlay {
261
+ z-index: var(--lt-z-overlay);
262
+ }
263
+ @utility z-lt-modal {
264
+ z-index: var(--lt-z-modal);
265
+ }
266
+ @utility z-lt-popover {
267
+ z-index: var(--lt-z-popover);
268
+ }
269
+ @utility z-lt-toast {
270
+ z-index: var(--lt-z-toast);
271
+ }
272
+
241
273
  @keyframes lt-toast-in {
242
274
  from {
243
275
  opacity: 0;
@@ -346,44 +378,44 @@
346
378
  * `:where()` keeps specificity low so consumers can override freely.
347
379
  */
348
380
  .lattice-prose :where(h1) {
349
- font-size: 1.5rem;
381
+ font-size: var(--text-4xl);
350
382
  line-height: 2rem;
351
- font-weight: 700;
352
- margin: 1rem 0 0.5rem;
383
+ font-weight: var(--lt-font-weight-bold);
384
+ margin: calc(var(--spacing) * 4) 0 calc(var(--spacing) * 2);
353
385
  }
354
386
 
355
387
  .lattice-prose :where(h2) {
356
- font-size: 1.25rem;
388
+ font-size: var(--text-3xl);
357
389
  line-height: 1.75rem;
358
- font-weight: 700;
359
- margin: 1rem 0 0.5rem;
390
+ font-weight: var(--lt-font-weight-bold);
391
+ margin: calc(var(--spacing) * 4) 0 calc(var(--spacing) * 2);
360
392
  }
361
393
 
362
394
  .lattice-prose :where(h3) {
363
- font-size: 1.125rem;
395
+ font-size: var(--text-2xl);
364
396
  line-height: 1.5rem;
365
- font-weight: 600;
366
- margin: 0.75rem 0 0.5rem;
397
+ font-weight: var(--lt-font-weight-semibold);
398
+ margin: calc(var(--spacing) * 3) 0 calc(var(--spacing) * 2);
367
399
  }
368
400
 
369
401
  .lattice-prose :where(p) {
370
- margin: 0.5rem 0;
402
+ margin: calc(var(--spacing) * 2) 0;
371
403
  }
372
404
 
373
405
  .lattice-prose :where(ul) {
374
406
  list-style: disc;
375
- padding-left: 1.5rem;
376
- margin: 0.5rem 0;
407
+ padding-left: calc(var(--spacing) * 6);
408
+ margin: calc(var(--spacing) * 2) 0;
377
409
  }
378
410
 
379
411
  .lattice-prose :where(ol) {
380
412
  list-style: decimal;
381
- padding-left: 1.5rem;
382
- margin: 0.5rem 0;
413
+ padding-left: calc(var(--spacing) * 6);
414
+ margin: calc(var(--spacing) * 2) 0;
383
415
  }
384
416
 
385
417
  .lattice-prose :where(li) {
386
- margin: 0.25rem 0;
418
+ margin: var(--spacing) 0;
387
419
  }
388
420
 
389
421
  .lattice-prose :where(li p) {
@@ -392,14 +424,14 @@
392
424
 
393
425
  .lattice-prose :where(blockquote) {
394
426
  border-left: 3px solid var(--lt-border);
395
- padding-left: 1rem;
396
- margin: 0.75rem 0;
427
+ padding-left: calc(var(--spacing) * 4);
428
+ margin: calc(var(--spacing) * 3) 0;
397
429
  color: var(--lt-muted-fg);
398
430
  font-style: italic;
399
431
  }
400
432
 
401
433
  .lattice-prose :where(strong) {
402
- font-weight: 700;
434
+ font-weight: var(--lt-font-weight-bold);
403
435
  }
404
436
 
405
437
  .lattice-prose :where(em) {
@@ -416,7 +448,7 @@
416
448
  }
417
449
 
418
450
  .lattice-prose :where(mark) {
419
- background: var(--lt-warning, #fef08a);
451
+ background: var(--lt-warning);
420
452
  color: inherit;
421
453
  border-radius: var(--lt-radius-sm);
422
454
  padding: 0.05em 0.15em;
@@ -431,7 +463,7 @@
431
463
  .lattice-prose :where(code) {
432
464
  background: var(--lt-muted);
433
465
  border-radius: var(--lt-radius-sm);
434
- padding: 0.125rem 0.25rem;
466
+ padding: calc(var(--spacing) * 0.5) var(--spacing);
435
467
  font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
436
468
  font-size: 0.875em;
437
469
  }
@@ -439,11 +471,11 @@
439
471
  .lattice-prose :where(pre) {
440
472
  background: var(--lt-muted);
441
473
  border-radius: var(--lt-radius-sm);
442
- padding: 0.75rem 1rem;
443
- margin: 0.75rem 0;
474
+ padding: calc(var(--spacing) * 3) calc(var(--spacing) * 4);
475
+ margin: calc(var(--spacing) * 3) 0;
444
476
  overflow-x: auto;
445
477
  font-family: ui-monospace, "SFMono-Regular", "Menlo", monospace;
446
- font-size: 0.875rem;
478
+ font-size: var(--text-lg);
447
479
  }
448
480
 
449
481
  .lattice-prose :where(pre code) {
@@ -455,27 +487,27 @@
455
487
  .lattice-prose :where(hr) {
456
488
  border: none;
457
489
  border-top: 1px solid var(--lt-border);
458
- margin: 1rem 0;
490
+ margin: calc(var(--spacing) * 4) 0;
459
491
  }
460
492
 
461
493
  .lattice-prose :where(table) {
462
494
  border-collapse: collapse;
463
495
  width: 100%;
464
- margin: 0.75rem 0;
496
+ margin: calc(var(--spacing) * 3) 0;
465
497
  table-layout: fixed;
466
498
  overflow: hidden;
467
499
  }
468
500
 
469
501
  .lattice-prose :where(th, td) {
470
502
  border: 1px solid var(--lt-border);
471
- padding: 0.375rem 0.5rem;
503
+ padding: calc(var(--spacing) * 1.5) calc(var(--spacing) * 2);
472
504
  text-align: left;
473
505
  vertical-align: top;
474
506
  }
475
507
 
476
508
  .lattice-prose :where(th) {
477
509
  background: var(--lt-muted);
478
- font-weight: 600;
510
+ font-weight: var(--lt-font-weight-semibold);
479
511
  }
480
512
 
481
513
  .lattice-prose :where(.selectedCell) {
@@ -485,13 +517,13 @@
485
517
  .lattice-prose :where(details) {
486
518
  border: 1px solid var(--lt-border);
487
519
  border-radius: var(--lt-radius-sm);
488
- padding: 0.5rem 0.75rem;
489
- margin: 0.75rem 0;
520
+ padding: calc(var(--spacing) * 2) calc(var(--spacing) * 3);
521
+ margin: calc(var(--spacing) * 3) 0;
490
522
  }
491
523
 
492
524
  .lattice-prose :where(summary) {
493
525
  cursor: pointer;
494
- font-weight: 600;
526
+ font-weight: var(--lt-font-weight-semibold);
495
527
  }
496
528
 
497
529
  .lattice-prose :where(:first-child) {
@@ -40,7 +40,7 @@ var MenuItemComponent = ({ children, node }) => {
40
40
  }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
41
41
  prefix ? /* @__PURE__ */ jsx(MenuAffix, { affix: prefix }) : null,
42
42
  collapsed ? /* @__PURE__ */ jsx("span", {
43
- className: "pointer-events-none absolute top-1/2 left-full z-50 ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block",
43
+ className: "pointer-events-none absolute top-1/2 left-full z-lt-popover ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block",
44
44
  role: "tooltip",
45
45
  children: label
46
46
  }) : /* @__PURE__ */ jsx("span", { children: label }),
@@ -1 +1 @@
1
- {"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { Node, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\n const icon = node.props.icon;\n const label = node.props.label;\n const prefix = node.props.prefix;\n const suffix = node.props.suffix;\n const iconOnly = Boolean(icon);\n const flyoutIcon = icon ?? prefix?.icon ?? null;\n const href = node.props.href ?? \"\";\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-50 ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n const action = node.props.action;\n\n if (action && href === \"\" && !children) {\n return (\n <ActionMenuItem\n action={action as Node<\"action\">}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n )}\n label={collapsed || iconOnly ? label : undefined}\n testId={testId}\n >\n {content}\n </ActionMenuItem>\n );\n }\n\n if (href === \"\") {\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n }\n\n const active = currentPath === href;\n const method = node.props.method ?? \"get\";\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={method === \"get\" ? undefined : \"button\"}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n active && \"bg-lt-muted font-medium\",\n )}\n data-test={testId}\n href={href}\n method={method}\n >\n {content}\n </Link>\n </li>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nfunction ActionMenuItem({\n action,\n children,\n className,\n label,\n testId,\n}: {\n action: Node<\"action\">;\n children: ReactNode;\n className: string;\n label?: string;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <li>\n <button\n aria-label={label}\n className={className}\n data-test={testId}\n disabled={processing}\n onClick={requestSubmit}\n type=\"button\"\n >\n {children}\n </button>\n {overlays}\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"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"}
1
+ {"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { Node, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\n const icon = node.props.icon;\n const label = node.props.label;\n const prefix = node.props.prefix;\n const suffix = node.props.suffix;\n const iconOnly = Boolean(icon);\n const flyoutIcon = icon ?? prefix?.icon ?? null;\n const href = node.props.href ?? \"\";\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-lt-popover ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n const action = node.props.action;\n\n if (action && href === \"\" && !children) {\n return (\n <ActionMenuItem\n action={action as Node<\"action\">}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n )}\n label={collapsed || iconOnly ? label : undefined}\n testId={testId}\n >\n {content}\n </ActionMenuItem>\n );\n }\n\n if (href === \"\") {\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n }\n\n const active = currentPath === href;\n const method = node.props.method ?? \"get\";\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={method === \"get\" ? undefined : \"button\"}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n active && \"bg-lt-muted font-medium\",\n )}\n data-test={testId}\n href={href}\n method={method}\n >\n {content}\n </Link>\n </li>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nfunction ActionMenuItem({\n action,\n children,\n className,\n label,\n testId,\n}: {\n action: Node<\"action\">;\n children: ReactNode;\n className: string;\n label?: string;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <li>\n <button\n aria-label={label}\n className={className}\n data-test={testId}\n disabled={processing}\n onClick={requestSubmit}\n type=\"button\"\n >\n {children}\n </button>\n {overlays}\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"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"}
@@ -70,11 +70,11 @@ var SidebarComponent = ({ children, node }) => {
70
70
  value: isCollapsed,
71
71
  children: [mobileOpen ? /* @__PURE__ */ jsx("div", {
72
72
  "aria-hidden": "true",
73
- className: "fixed inset-0 z-30 bg-black/50 md:hidden",
73
+ className: "fixed inset-0 z-lt-overlay bg-lt-overlay md:hidden",
74
74
  "data-test": "sidebar-backdrop",
75
75
  onClick: () => setMobileOpen(false)
76
76
  }) : null, /* @__PURE__ */ jsx("aside", {
77
- className: cn("fixed inset-y-0 left-0 z-40 flex h-svh w-72 max-w-[80vw] shrink-0 flex-col gap-4 border-r border-lt-border bg-lt-bg p-4 transition-transform", "md:sticky md:top-0 md:z-auto md:max-w-none md:translate-x-0 md:transition-[width]", mobileOpen ? "translate-x-0" : "-translate-x-full", isCollapsed ? "md:w-16 md:overflow-visible" : "md:w-64 md:overflow-x-hidden md:overflow-y-auto"),
77
+ className: cn("fixed inset-y-0 left-0 z-lt-modal flex h-svh w-72 max-w-[80vw] shrink-0 flex-col gap-4 border-r border-lt-border bg-lt-bg p-4 transition-transform", "md:sticky md:top-0 md:z-auto md:max-w-none md:translate-x-0 md:transition-[width]", mobileOpen ? "translate-x-0" : "-translate-x-full", isCollapsed ? "md:w-16 md:overflow-visible" : "md:w-64 md:overflow-x-hidden md:overflow-y-auto"),
78
78
  "data-collapsed": isCollapsed ? "true" : "false",
79
79
  "data-lattice-component": identity,
80
80
  "data-test": "sidebar",