@lattice-php/lattice 0.18.0 → 0.20.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 (255) hide show
  1. package/README.md +1 -1
  2. package/dist/core/index.js +3 -3
  3. package/dist/core/nodes.d.ts +5 -0
  4. package/dist/core/nodes.js +8 -1
  5. package/dist/core/nodes.js.map +1 -1
  6. package/dist/core/renderer.js +1 -3
  7. package/dist/core/renderer.js.map +1 -1
  8. package/dist/core/types.d.ts +16 -4
  9. package/dist/create-app.d.ts +31 -3
  10. package/dist/create-app.js +48 -10
  11. package/dist/create-app.js.map +1 -1
  12. package/dist/effects/registry.d.ts +3 -3
  13. package/dist/effects/registry.js +4 -4
  14. package/dist/effects/registry.js.map +1 -1
  15. package/dist/events/event-names.d.ts +0 -3
  16. package/dist/events/event-names.js +0 -3
  17. package/dist/events/event-names.js.map +1 -1
  18. package/dist/form/components/base/field.d.ts +2 -2
  19. package/dist/form/components/base/field.js +2 -2
  20. package/dist/form/components/base/field.js.map +1 -1
  21. package/dist/form/components/fields/color-picker-field.d.ts +2 -0
  22. package/dist/form/components/fields/color-picker-field.js +57 -0
  23. package/dist/form/components/fields/color-picker-field.js.map +1 -0
  24. package/dist/form/components/fields/date-input.js +1 -1
  25. package/dist/form/components/fields/date-picker-field.js +1 -1
  26. package/dist/form/components/fields/date-time-input.js +1 -1
  27. package/dist/form/components/fields/file-upload.js +7 -12
  28. package/dist/form/components/fields/file-upload.js.map +1 -1
  29. package/dist/form/components/fields/number-input.js +1 -1
  30. package/dist/form/components/fields/rich-editor-field.js +36 -275
  31. package/dist/form/components/fields/rich-editor-field.js.map +1 -1
  32. package/dist/form/components/fields/row-action-menu.js +2 -2
  33. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  34. package/dist/form/components/fields/row-actions.js +3 -1
  35. package/dist/form/components/fields/row-actions.js.map +1 -1
  36. package/dist/form/components/fields/row-item.js +2 -1
  37. package/dist/form/components/fields/row-item.js.map +1 -1
  38. package/dist/form/components/fields/select.js +59 -16
  39. package/dist/form/components/fields/select.js.map +1 -1
  40. package/dist/form/components/fields/table-rows.js +5 -4
  41. package/dist/form/components/fields/table-rows.js.map +1 -1
  42. package/dist/form/components/fields/text-input.js +8 -1
  43. package/dist/form/components/fields/text-input.js.map +1 -1
  44. package/dist/form/components/fields/time-input.js +1 -1
  45. package/dist/form/components/index.d.ts +1 -0
  46. package/dist/form/components/index.js +2 -1
  47. package/dist/form/embed.d.ts +1 -0
  48. package/dist/form/embed.js +2 -1
  49. package/dist/form/hooks/use-form-resolver.js.map +1 -1
  50. package/dist/form/index.js +2 -1
  51. package/dist/form/lib/conditions.d.ts +4 -10
  52. package/dist/form/lib/conditions.js.map +1 -1
  53. package/dist/form/lib/field-props.d.ts +4 -18
  54. package/dist/form/lib/field-props.js.map +1 -1
  55. package/dist/form/plugin.js +2 -0
  56. package/dist/form/plugin.js.map +1 -1
  57. package/dist/form/rich-editor/builtins.d.ts +6 -0
  58. package/dist/form/rich-editor/builtins.js +400 -0
  59. package/dist/form/rich-editor/builtins.js.map +1 -0
  60. package/dist/form/rich-editor/index.d.ts +7 -0
  61. package/dist/form/rich-editor/index.js +3 -0
  62. package/dist/form/rich-editor/registry.d.ts +67 -0
  63. package/dist/form/rich-editor/registry.js +79 -0
  64. package/dist/form/rich-editor/registry.js.map +1 -0
  65. package/dist/form/rich-editor/toolbar-button.d.ts +12 -0
  66. package/dist/form/rich-editor/toolbar-button.js +28 -0
  67. package/dist/form/rich-editor/toolbar-button.js.map +1 -0
  68. package/dist/form/types.d.ts +1 -6
  69. package/dist/format/date-time.d.ts +3 -2
  70. package/dist/format/date-time.js.map +1 -1
  71. package/dist/i18n/backend.js +1 -1
  72. package/dist/i18n/date-time.d.ts +3 -2
  73. package/dist/i18n/date-time.js.map +1 -1
  74. package/dist/i18n/index.js +3 -2
  75. package/dist/i18n/instance.js +1 -1
  76. package/dist/i18n/page-props.d.ts +3 -2
  77. package/dist/i18n/page-props.js +1 -15
  78. package/dist/i18n/page-props.js.map +1 -1
  79. package/dist/i18n/shared-props.d.ts +7 -0
  80. package/dist/i18n/shared-props.js +25 -0
  81. package/dist/i18n/shared-props.js.map +1 -0
  82. package/dist/icons/icon-renderer.js.map +1 -1
  83. package/dist/index.d.ts +5 -3
  84. package/dist/index.js +7 -6
  85. package/dist/lattice.css +119 -57
  86. package/dist/layout/components/breadcrumbs.js +3 -1
  87. package/dist/layout/components/breadcrumbs.js.map +1 -1
  88. package/dist/layout/components/callouts.js +1 -1
  89. package/dist/lib/color.d.ts +9 -0
  90. package/dist/lib/color.js +48 -0
  91. package/dist/lib/color.js.map +1 -0
  92. package/dist/notifications/api.d.ts +6 -6
  93. package/dist/notifications/api.js.map +1 -1
  94. package/dist/notifications/types.d.ts +2 -10
  95. package/dist/provider-base.js +1 -1
  96. package/dist/realtime/build-effects.js +5 -5
  97. package/dist/realtime/build-effects.js.map +1 -1
  98. package/dist/realtime/listeners.d.ts +2 -2
  99. package/dist/realtime/listeners.js.map +1 -1
  100. package/dist/realtime/subscriptions.d.ts +2 -2
  101. package/dist/realtime/subscriptions.js.map +1 -1
  102. package/dist/registry.js.map +1 -1
  103. package/dist/table/components/bulk-bar.js +5 -5
  104. package/dist/table/components/bulk-bar.js.map +1 -1
  105. package/dist/table/components/cells/badge-cell.js +4 -1
  106. package/dist/table/components/cells/badge-cell.js.map +1 -1
  107. package/dist/table/components/cells/boolean-cell.js +3 -1
  108. package/dist/table/components/cells/boolean-cell.js.map +1 -1
  109. package/dist/table/components/cells/icon-cell.js +5 -2
  110. package/dist/table/components/cells/icon-cell.js.map +1 -1
  111. package/dist/table/components/cells/image-cell.js +8 -9
  112. package/dist/table/components/cells/image-cell.js.map +1 -1
  113. package/dist/table/components/cells/money-cell.js +9 -1
  114. package/dist/table/components/cells/money-cell.js.map +1 -1
  115. package/dist/table/components/cells/number-cell.js +9 -1
  116. package/dist/table/components/cells/number-cell.js.map +1 -1
  117. package/dist/table/components/cells/text-cell.js +4 -1
  118. package/dist/table/components/cells/text-cell.js.map +1 -1
  119. package/dist/table/components/column-filter-control.js +25 -32
  120. package/dist/table/components/column-filter-control.js.map +1 -1
  121. package/dist/table/components/column-header.d.ts +3 -3
  122. package/dist/table/components/column-header.js +5 -3
  123. package/dist/table/components/column-header.js.map +1 -1
  124. package/dist/table/components/column-visibility-menu.js +6 -11
  125. package/dist/table/components/column-visibility-menu.js.map +1 -1
  126. package/dist/table/components/filter-bar.d.ts +2 -2
  127. package/dist/table/components/filter-bar.js +13 -22
  128. package/dist/table/components/filter-bar.js.map +1 -1
  129. package/dist/table/components/filter-controls.d.ts +2 -1
  130. package/dist/table/components/filter-controls.js +36 -35
  131. package/dist/table/components/filter-controls.js.map +1 -1
  132. package/dist/table/components/filter-value-input.d.ts +0 -1
  133. package/dist/table/components/filter-value-input.js +21 -20
  134. package/dist/table/components/filter-value-input.js.map +1 -1
  135. package/dist/table/components/pagination.js +16 -20
  136. package/dist/table/components/pagination.js.map +1 -1
  137. package/dist/table/components/sort-bar.d.ts +3 -3
  138. package/dist/table/components/sort-bar.js +11 -12
  139. package/dist/table/components/sort-bar.js.map +1 -1
  140. package/dist/table/components/table-search.d.ts +9 -0
  141. package/dist/table/components/table-search.js +75 -0
  142. package/dist/table/components/table-search.js.map +1 -0
  143. package/dist/table/components/table.js +72 -19
  144. package/dist/table/components/table.js.map +1 -1
  145. package/dist/table/hooks/use-expanded-rows.d.ts +8 -0
  146. package/dist/table/hooks/use-expanded-rows.js +24 -0
  147. package/dist/table/hooks/use-expanded-rows.js.map +1 -0
  148. package/dist/table/hooks/use-table.d.ts +5 -3
  149. package/dist/table/hooks/use-table.js +56 -44
  150. package/dist/table/hooks/use-table.js.map +1 -1
  151. package/dist/table/lib/payload.d.ts +3 -2
  152. package/dist/table/lib/payload.js +16 -10
  153. package/dist/table/lib/payload.js.map +1 -1
  154. package/dist/table/lib/query.d.ts +5 -5
  155. package/dist/table/lib/query.js +20 -18
  156. package/dist/table/lib/query.js.map +1 -1
  157. package/dist/table/types.d.ts +3 -26
  158. package/dist/toast/callout.js +1 -3
  159. package/dist/toast/callout.js.map +1 -1
  160. package/dist/toast/toast.d.ts +1 -5
  161. package/dist/toast/toast.js +2 -7
  162. package/dist/toast/toast.js.map +1 -1
  163. package/dist/toast/toaster.js +2 -2
  164. package/dist/toast/toaster.js.map +1 -1
  165. package/dist/types/generated.d.ts +263 -188
  166. package/dist/ui/affix-group.d.ts +2 -1
  167. package/dist/ui/affix-group.js +9 -7
  168. package/dist/ui/affix-group.js.map +1 -1
  169. package/dist/ui/avatar.d.ts +3 -0
  170. package/dist/ui/avatar.js +52 -0
  171. package/dist/ui/avatar.js.map +1 -0
  172. package/dist/ui/button.d.ts +3 -1
  173. package/dist/ui/button.js +10 -3
  174. package/dist/ui/button.js.map +1 -1
  175. package/dist/ui/chart-view.js +161 -11
  176. package/dist/ui/chart-view.js.map +1 -1
  177. package/dist/ui/click-behavior.d.ts +1 -1
  178. package/dist/ui/click-behavior.js.map +1 -1
  179. package/dist/ui/collapsible.js +1 -1
  180. package/dist/ui/color-picker.d.ts +8 -0
  181. package/dist/ui/color-picker.js +59 -0
  182. package/dist/ui/color-picker.js.map +1 -0
  183. package/dist/ui/combobox.d.ts +11 -2
  184. package/dist/ui/combobox.js +51 -8
  185. package/dist/ui/combobox.js.map +1 -1
  186. package/dist/ui/copyable-text.d.ts +7 -0
  187. package/dist/ui/copyable-text.js +21 -13
  188. package/dist/ui/copyable-text.js.map +1 -1
  189. package/dist/ui/fragment.js +1 -1
  190. package/dist/ui/fragment.js.map +1 -1
  191. package/dist/ui/heading.js +39 -21
  192. package/dist/ui/heading.js.map +1 -1
  193. package/dist/ui/icon-button.d.ts +22 -0
  194. package/dist/ui/icon-button.js +51 -0
  195. package/dist/ui/icon-button.js.map +1 -0
  196. package/dist/ui/icon.js +10 -11
  197. package/dist/ui/icon.js.map +1 -1
  198. package/dist/ui/image-preview.d.ts +12 -0
  199. package/dist/ui/image-preview.js +65 -0
  200. package/dist/ui/image-preview.js.map +1 -0
  201. package/dist/ui/image.d.ts +3 -0
  202. package/dist/ui/image.js +21 -0
  203. package/dist/ui/image.js.map +1 -0
  204. package/dist/ui/index.d.ts +2 -1
  205. package/dist/ui/index.js +4 -3
  206. package/dist/ui/info-tooltip.js +1 -1
  207. package/dist/ui/info-tooltip.js.map +1 -1
  208. package/dist/ui/input.d.ts +3 -1
  209. package/dist/ui/input.js +2 -2
  210. package/dist/ui/input.js.map +1 -1
  211. package/dist/ui/modal.js +14 -2
  212. package/dist/ui/modal.js.map +1 -1
  213. package/dist/ui/native-select.d.ts +10 -0
  214. package/dist/ui/native-select.js +23 -0
  215. package/dist/ui/native-select.js.map +1 -0
  216. package/dist/ui/plugin.js +11 -1
  217. package/dist/ui/plugin.js.map +1 -1
  218. package/dist/ui/progress.d.ts +3 -0
  219. package/dist/ui/progress.js +114 -0
  220. package/dist/ui/progress.js.map +1 -0
  221. package/dist/ui/section.js +1 -1
  222. package/dist/ui/separator.d.ts +3 -0
  223. package/dist/ui/separator.js +15 -0
  224. package/dist/ui/separator.js.map +1 -0
  225. package/dist/ui/text.js +5 -10
  226. package/dist/ui/text.js.map +1 -1
  227. package/dist/ui/tooltip.js +1 -1
  228. package/dist/ui/tree-context.d.ts +33 -0
  229. package/dist/ui/tree-context.js +137 -0
  230. package/dist/ui/tree-context.js.map +1 -0
  231. package/dist/ui/tree.d.ts +19 -0
  232. package/dist/ui/tree.js +187 -0
  233. package/dist/ui/tree.js.map +1 -0
  234. package/dist-standalone/chunks/chart-view-wJmnVu7L.js +36 -0
  235. package/dist-standalone/chunks/date-picker-field-VbH9xrQO.js +1 -0
  236. package/dist-standalone/chunks/dist-PaT1gYi_.js +2 -0
  237. package/dist-standalone/chunks/jsx-runtime-Drll_-DD.js +1 -0
  238. package/dist-standalone/chunks/locale-CXqr7LPR.js +1 -0
  239. package/dist-standalone/chunks/notifications-echo-DsfNc4Ge.js +1 -0
  240. package/dist-standalone/chunks/page-props-AIa5UR6n.js +1 -0
  241. package/dist-standalone/chunks/react-B8IZ02wI.js +1 -0
  242. package/dist-standalone/chunks/rich-editor-field-DLQTyt36.js +154 -0
  243. package/dist-standalone/chunks/subscriptions-DY2tj9Im.js +1 -0
  244. package/dist-standalone/chunks/test-id-Dh3L6Gfh.js +1 -0
  245. package/dist-standalone/chunks/use-effect-dispatcher-feEs0GCP.js +97 -0
  246. package/dist-standalone/chunks/with-selector-BwDoCrJJ.js +1 -0
  247. package/dist-standalone/lattice.css +3 -0
  248. package/dist-standalone/lattice.js +66 -0
  249. package/dist-standalone/manifest.json +20 -0
  250. package/dist-standalone/sprite.svg +1 -0
  251. package/package.json +11 -2
  252. package/resources/icons/code-xml.svg +17 -0
  253. package/resources/icons/heading.svg +17 -0
  254. package/dist/action/hooks/use-click-behavior.d.ts +0 -3
  255. package/dist/action/hooks/use-click-behavior.js +0 -3
@@ -0,0 +1,59 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Input } from "./input.js";
3
+ import { useState } from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { HexColorPicker } from "react-colorful";
6
+ //#region resources/js/ui/color-picker.tsx
7
+ function normalizeHex(input) {
8
+ const raw = input.trim().replace(/^#/, "").toLowerCase();
9
+ if (/^[0-9a-f]{6}$/.test(raw)) return `#${raw}`;
10
+ if (/^[0-9a-f]{3}$/.test(raw)) return `#${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}`;
11
+ return null;
12
+ }
13
+ function ColorPicker({ value, onChange, palette, hexLabel = "Hex color", paletteLabel = "Color palette" }) {
14
+ const [draft, setDraft] = useState(null);
15
+ const color = normalizeHex(value) ?? "#6b7280";
16
+ return /* @__PURE__ */ jsxs("div", {
17
+ className: "flex w-56 flex-col gap-3",
18
+ "data-slot": "color-picker",
19
+ children: [
20
+ /* @__PURE__ */ jsx(HexColorPicker, {
21
+ className: "!w-full",
22
+ color,
23
+ onChange
24
+ }),
25
+ palette.length > 0 && /* @__PURE__ */ jsx("div", {
26
+ "aria-label": paletteLabel,
27
+ className: "flex flex-wrap gap-1.5",
28
+ role: "listbox",
29
+ children: palette.map((swatch) => {
30
+ const normalized = normalizeHex(swatch) ?? swatch;
31
+ return /* @__PURE__ */ jsx("button", {
32
+ "aria-label": normalized,
33
+ "aria-selected": normalized === color,
34
+ className: cn("size-6 shrink-0 rounded-full border border-lt-border", normalized === color && "ring-2 ring-lt-ring ring-offset-1"),
35
+ onClick: () => onChange(normalized),
36
+ role: "option",
37
+ style: { background: swatch },
38
+ type: "button"
39
+ }, swatch);
40
+ })
41
+ }),
42
+ /* @__PURE__ */ jsx(Input, {
43
+ "aria-label": hexLabel,
44
+ onBlur: () => setDraft(null),
45
+ onChange: (event) => {
46
+ const text = event.target.value;
47
+ const hex = normalizeHex(text);
48
+ setDraft(text);
49
+ if (hex !== null) onChange(hex);
50
+ },
51
+ value: draft ?? color
52
+ })
53
+ ]
54
+ });
55
+ }
56
+ //#endregion
57
+ export { ColorPicker, normalizeHex };
58
+
59
+ //# sourceMappingURL=color-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-picker.js","names":[],"sources":["../../resources/js/ui/color-picker.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { HexColorPicker } from \"react-colorful\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Input } from \"./input\";\n\nexport function normalizeHex(input: string): string | null {\n const raw = input.trim().replace(/^#/, \"\").toLowerCase();\n\n if (/^[0-9a-f]{6}$/.test(raw)) {\n return `#${raw}`;\n }\n\n if (/^[0-9a-f]{3}$/.test(raw)) {\n return `#${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}`;\n }\n\n return null;\n}\n\nexport function ColorPicker({\n value,\n onChange,\n palette,\n hexLabel = \"Hex color\",\n paletteLabel = \"Color palette\",\n}: {\n value: string;\n onChange: (hex: string) => void;\n palette: string[];\n hexLabel?: string;\n paletteLabel?: string;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n const color = normalizeHex(value) ?? \"#6b7280\";\n\n return (\n <div className=\"flex w-56 flex-col gap-3\" data-slot=\"color-picker\">\n <HexColorPicker className=\"!w-full\" color={color} onChange={onChange} />\n {palette.length > 0 && (\n <div aria-label={paletteLabel} className=\"flex flex-wrap gap-1.5\" role=\"listbox\">\n {palette.map((swatch) => {\n const normalized = normalizeHex(swatch) ?? swatch;\n\n return (\n <button\n aria-label={normalized}\n aria-selected={normalized === color}\n className={cn(\n \"size-6 shrink-0 rounded-full border border-lt-border\",\n normalized === color && \"ring-2 ring-lt-ring ring-offset-1\",\n )}\n key={swatch}\n onClick={() => onChange(normalized)}\n role=\"option\"\n style={{ background: swatch }}\n type=\"button\"\n />\n );\n })}\n </div>\n )}\n <Input\n aria-label={hexLabel}\n onBlur={() => setDraft(null)}\n onChange={(event) => {\n const text = event.target.value;\n const hex = normalizeHex(text);\n setDraft(text);\n\n if (hex !== null) {\n onChange(hex);\n }\n }}\n value={draft ?? color}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;AAKA,SAAgB,aAAa,OAA8B;CACzD,MAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,EAAE,EAAE,YAAY;CAEvD,IAAI,gBAAgB,KAAK,GAAG,GAC1B,OAAO,IAAI;CAGb,IAAI,gBAAgB,KAAK,GAAG,GAC1B,OAAO,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI;CAG9D,OAAO;AACT;AAEA,SAAgB,YAAY,EAC1B,OACA,UACA,SACA,WAAW,aACX,eAAe,mBAOd;CACD,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CACtD,MAAM,QAAQ,aAAa,KAAK,KAAK;CAErC,OACE,qBAAC,OAAD;EAAK,WAAU;EAA2B,aAAU;YAApD;GACE,oBAAC,gBAAD;IAAgB,WAAU;IAAiB;IAAiB;GAAW,CAAA;GACtE,QAAQ,SAAS,KAChB,oBAAC,OAAD;IAAK,cAAY;IAAc,WAAU;IAAyB,MAAK;cACpE,QAAQ,KAAK,WAAW;KACvB,MAAM,aAAa,aAAa,MAAM,KAAK;KAE3C,OACE,oBAAC,UAAD;MACE,cAAY;MACZ,iBAAe,eAAe;MAC9B,WAAW,GACT,wDACA,eAAe,SAAS,mCAC1B;MAEA,eAAe,SAAS,UAAU;MAClC,MAAK;MACL,OAAO,EAAE,YAAY,OAAO;MAC5B,MAAK;KACN,GALM,MAKN;IAEL,CAAC;GACE,CAAA;GAEP,oBAAC,OAAD;IACE,cAAY;IACZ,cAAc,SAAS,IAAI;IAC3B,WAAW,UAAU;KACnB,MAAM,OAAO,MAAM,OAAO;KAC1B,MAAM,MAAM,aAAa,IAAI;KAC7B,SAAS,IAAI;KAEb,IAAI,QAAQ,MACV,SAAS,GAAG;IAEhB;IACA,OAAO,SAAS;GACjB,CAAA;EACE;;AAET"}
@@ -7,18 +7,27 @@ import * as React from "react";
7
7
  * consumer also owns option fetching. Pass `onSearch` for remote search (the
8
8
  * combobox debounces the query and renders `options` as given); omit it to
9
9
  * filter the provided `options` locally by label. The combobox closes itself
10
- * after a single-select.
10
+ * after a single-select. Pass `renderOption` to render rich option rows; the
11
+ * option's label stays the accessible name. `onSelect` toggles selection
12
+ * (used by dropdown-row clicks); tag-entry commits (Enter/comma/paste) that
13
+ * match an existing option call `onCommit` instead, falling back to
14
+ * `onSelect` when it is not provided, so consumers can make tag entry
15
+ * additive instead of toggling.
11
16
  */
12
- declare function Combobox({ contentClassName, emptyLabel, loading, multiple, onSearch, onSelect, open, onOpenChange, options, searchLabel, searchPlaceholder, selected, showSearch, testId, trigger, triggerClassName, triggerProps, }: {
17
+ declare function Combobox({ contentClassName, creatable, emptyLabel, loading, multiple, onCommit, onCreate, onSearch, onSelect, open, onOpenChange, options, renderOption, searchLabel, searchPlaceholder, selected, showSearch, testId, trigger, triggerClassName, triggerProps, }: {
13
18
  contentClassName?: string;
19
+ creatable?: boolean;
14
20
  emptyLabel?: string;
15
21
  loading?: boolean;
16
22
  multiple?: boolean;
23
+ onCommit?: (value: string) => void;
24
+ onCreate?: (label: string) => void;
17
25
  onSearch?: (query: string) => void;
18
26
  onSelect: (value: string) => void;
19
27
  open: boolean;
20
28
  onOpenChange: (open: boolean) => void;
21
29
  options: Option[];
30
+ renderOption?: (option: Option) => React.ReactNode;
22
31
  searchLabel?: string;
23
32
  searchPlaceholder?: string;
24
33
  selected: string[];
@@ -13,11 +13,27 @@ var SEARCH_DEBOUNCE_MS = 250;
13
13
  * consumer also owns option fetching. Pass `onSearch` for remote search (the
14
14
  * combobox debounces the query and renders `options` as given); omit it to
15
15
  * filter the provided `options` locally by label. The combobox closes itself
16
- * after a single-select.
16
+ * after a single-select. Pass `renderOption` to render rich option rows; the
17
+ * option's label stays the accessible name. `onSelect` toggles selection
18
+ * (used by dropdown-row clicks); tag-entry commits (Enter/comma/paste) that
19
+ * match an existing option call `onCommit` instead, falling back to
20
+ * `onSelect` when it is not provided, so consumers can make tag entry
21
+ * additive instead of toggling.
17
22
  */
18
- function Combobox({ contentClassName, emptyLabel, loading = false, multiple = false, onSearch, onSelect, open, onOpenChange, options, searchLabel, searchPlaceholder, selected, showSearch = true, testId, trigger, triggerClassName, triggerProps }) {
23
+ function Combobox({ contentClassName, creatable = false, emptyLabel, loading = false, multiple = false, onCommit, onCreate, onSearch, onSelect, open, onOpenChange, options, renderOption, searchLabel, searchPlaceholder, selected, showSearch = true, testId, trigger, triggerClassName, triggerProps }) {
19
24
  const { t } = useT("lattice");
20
25
  const [query, setQuery] = useState("");
26
+ function commitCreate(raw) {
27
+ const tokens = raw.split(",").map((token) => token.trim()).filter(Boolean);
28
+ for (const token of tokens) {
29
+ const match = options.find((option) => option.label.toLowerCase() === token.toLowerCase());
30
+ if (match) (onCommit ?? onSelect)(match.value);
31
+ else onCreate?.(token);
32
+ }
33
+ if (multiple) setQuery("");
34
+ else close();
35
+ }
36
+ const exactMatch = options.some((option) => option.label.toLowerCase() === query.trim().toLowerCase());
21
37
  useEffect(() => {
22
38
  if (!onSearch || !open) return;
23
39
  const timer = window.setTimeout(() => onSearch(query), SEARCH_DEBOUNCE_MS);
@@ -53,11 +69,24 @@ function Combobox({ contentClassName, emptyLabel, loading = false, multiple = fa
53
69
  children: [showSearch && /* @__PURE__ */ jsxs("div", {
54
70
  className: "flex items-center gap-2 border-b border-lt-border px-3 py-2",
55
71
  children: [/* @__PURE__ */ jsx("input", {
56
- "aria-label": searchLabel ?? t("form.searchOptions", "Search options"),
72
+ "aria-label": searchLabel ?? t("form.search-options", "Search options"),
57
73
  "data-slot": "combobox-search",
58
74
  "data-test": testId ? `${testId}-search` : void 0,
59
75
  className: "w-full bg-transparent text-sm outline-none placeholder:text-lt-muted-fg",
60
- onChange: (event) => setQuery(event.target.value),
76
+ onChange: (event) => {
77
+ const next = event.target.value;
78
+ if (creatable && next.includes(",")) {
79
+ commitCreate(next);
80
+ return;
81
+ }
82
+ setQuery(next);
83
+ },
84
+ onKeyDown: (event) => {
85
+ if (creatable && event.key === "Enter" && query.trim() !== "") {
86
+ event.preventDefault();
87
+ commitCreate(query);
88
+ }
89
+ },
61
90
  placeholder: searchPlaceholder,
62
91
  value: query
63
92
  }), loading && /* @__PURE__ */ jsx(Icon, {
@@ -65,15 +94,26 @@ function Combobox({ contentClassName, emptyLabel, loading = false, multiple = fa
65
94
  "aria-hidden": "true",
66
95
  className: "size-lt-icon-md shrink-0 animate-spin text-lt-muted-fg"
67
96
  })]
68
- }), /* @__PURE__ */ jsx("div", {
97
+ }), /* @__PURE__ */ jsxs("div", {
69
98
  className: "max-h-60 overflow-y-auto p-1",
70
99
  role: "listbox",
71
- children: visibleOptions.length === 0 ? /* @__PURE__ */ jsx("p", {
100
+ children: [creatable && query.trim() !== "" && !exactMatch && /* @__PURE__ */ jsxs("button", {
101
+ className: "flex w-full items-center gap-2 rounded-lt-sm px-3 py-1.5 text-left text-sm transition-colors hover:bg-lt-accent hover:text-lt-accent-fg",
102
+ "data-test": testId ? `${testId}-create` : void 0,
103
+ onClick: () => commitCreate(query),
104
+ type: "button",
105
+ children: [/* @__PURE__ */ jsx(Icon, {
106
+ name: "plus",
107
+ "aria-hidden": "true",
108
+ className: "size-lt-icon-md shrink-0"
109
+ }), t("form.create-option", "Create \"{{label}}\"", { label: query.trim() })]
110
+ }), visibleOptions.length === 0 && !(creatable && query.trim() !== "" && !exactMatch) ? /* @__PURE__ */ jsx("p", {
72
111
  className: "px-3 py-2 text-sm text-lt-muted-fg",
73
112
  children: emptyLabel
74
113
  }) : visibleOptions.map((option) => {
75
114
  const isSelected = selected.includes(option.value);
76
115
  return /* @__PURE__ */ jsxs("button", {
116
+ "aria-label": renderOption ? option.label : void 0,
77
117
  "aria-selected": isSelected,
78
118
  className: cn("flex w-full items-center justify-between gap-2 rounded-lt-sm px-3 py-1.5 text-left text-sm transition-colors hover:bg-lt-accent hover:text-lt-accent-fg", isSelected && "bg-lt-accent/60"),
79
119
  "data-slot": "combobox-option",
@@ -82,13 +122,16 @@ function Combobox({ contentClassName, emptyLabel, loading = false, multiple = fa
82
122
  onClick: () => choose(option.value),
83
123
  role: "option",
84
124
  type: "button",
85
- children: [option.label, isSelected && /* @__PURE__ */ jsx(Icon, {
125
+ children: [renderOption ? /* @__PURE__ */ jsx("span", {
126
+ className: "flex min-w-0 flex-1 items-center gap-2",
127
+ children: renderOption(option)
128
+ }) : option.label, isSelected && /* @__PURE__ */ jsx(Icon, {
86
129
  name: "check",
87
130
  "aria-hidden": "true",
88
131
  className: "size-lt-icon-md shrink-0"
89
132
  })]
90
133
  }, option.value);
91
- })
134
+ })]
92
135
  })]
93
136
  })]
94
137
  });
@@ -1 +1 @@
1
- {"version":3,"file":"combobox.js","names":[],"sources":["../../resources/js/ui/combobox.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport * as React from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { Option } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./popover\";\n\nconst SEARCH_DEBOUNCE_MS = 250;\n\n/**\n * A popover select list with an optional search box and single/multi selection.\n *\n * Selection state is controlled by the consumer (`selected` + `onSelect`); the\n * consumer also owns option fetching. Pass `onSearch` for remote search (the\n * combobox debounces the query and renders `options` as given); omit it to\n * filter the provided `options` locally by label. The combobox closes itself\n * after a single-select.\n */\nfunction Combobox({\n contentClassName,\n emptyLabel,\n loading = false,\n multiple = false,\n onSearch,\n onSelect,\n open,\n onOpenChange,\n options,\n searchLabel,\n searchPlaceholder,\n selected,\n showSearch = true,\n testId,\n trigger,\n triggerClassName,\n triggerProps,\n}: {\n contentClassName?: string;\n emptyLabel?: string;\n loading?: boolean;\n multiple?: boolean;\n onSearch?: (query: string) => void;\n onSelect: (value: string) => void;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n options: Option[];\n searchLabel?: string;\n searchPlaceholder?: string;\n selected: string[];\n showSearch?: boolean;\n testId?: string;\n trigger: React.ReactNode;\n triggerClassName?: string;\n triggerProps?: React.ComponentProps<\"button\"> & { \"data-test\"?: string };\n}) {\n const { t } = useT(\"lattice\");\n const [query, setQuery] = useState(\"\");\n\n useEffect(() => {\n if (!onSearch || !open) {\n return;\n }\n\n const timer = window.setTimeout(() => onSearch(query), SEARCH_DEBOUNCE_MS);\n\n return () => window.clearTimeout(timer);\n }, [query, onSearch, open]);\n\n const visibleOptions = onSearch\n ? options\n : options.filter((option) => option.label.toLowerCase().includes(query.toLowerCase()));\n\n function close(): void {\n setQuery(\"\");\n onOpenChange(false);\n }\n\n function choose(value: string): void {\n onSelect(value);\n\n if (!multiple) {\n close();\n }\n }\n\n return (\n <Popover open={open} onOpenChange={(next) => (next ? onOpenChange(true) : close())}>\n <PopoverTrigger asChild>\n <button type=\"button\" className={triggerClassName} {...triggerProps}>\n {trigger}\n </button>\n </PopoverTrigger>\n\n <PopoverContent\n align=\"start\"\n className={cn(\n \"w-[var(--radix-popover-trigger-width)] overflow-hidden p-0\",\n contentClassName,\n )}\n >\n {showSearch && (\n <div className=\"flex items-center gap-2 border-b border-lt-border px-3 py-2\">\n <input\n aria-label={searchLabel ?? t(\"form.searchOptions\", \"Search options\")}\n data-slot=\"combobox-search\"\n data-test={testId ? `${testId}-search` : undefined}\n className=\"w-full bg-transparent text-sm outline-none placeholder:text-lt-muted-fg\"\n onChange={(event) => setQuery(event.target.value)}\n placeholder={searchPlaceholder}\n value={query}\n />\n {loading && (\n <Icon\n name=\"loader-2\"\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0 animate-spin text-lt-muted-fg\"\n />\n )}\n </div>\n )}\n\n <div className=\"max-h-60 overflow-y-auto p-1\" role=\"listbox\">\n {visibleOptions.length === 0 ? (\n <p className=\"px-3 py-2 text-sm text-lt-muted-fg\">{emptyLabel}</p>\n ) : (\n visibleOptions.map((option) => {\n const isSelected = selected.includes(option.value);\n\n return (\n <button\n aria-selected={isSelected}\n className={cn(\n \"flex w-full items-center justify-between gap-2 rounded-lt-sm px-3 py-1.5 text-left text-sm transition-colors hover:bg-lt-accent hover:text-lt-accent-fg\",\n isSelected && \"bg-lt-accent/60\",\n )}\n data-slot=\"combobox-option\"\n data-test={testId ? `${testId}-option-${option.value}` : undefined}\n data-value={option.value}\n key={option.value}\n onClick={() => choose(option.value)}\n role=\"option\"\n type=\"button\"\n >\n {option.label}\n {isSelected && (\n <Icon name=\"check\" aria-hidden=\"true\" className=\"size-lt-icon-md shrink-0\" />\n )}\n </button>\n );\n })\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { Combobox };\n"],"mappings":";;;;;;;AAQA,IAAM,qBAAqB;;;;;;;;;;AAW3B,SAAS,SAAS,EAChB,kBACA,YACA,UAAU,OACV,WAAW,OACX,UACA,UACA,MACA,cACA,SACA,aACA,mBACA,UACA,aAAa,MACb,QACA,SACA,kBACA,gBAmBC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CAErC,gBAAgB;EACd,IAAI,CAAC,YAAY,CAAC,MAChB;EAGF,MAAM,QAAQ,OAAO,iBAAiB,SAAS,KAAK,GAAG,kBAAkB;EAEzE,aAAa,OAAO,aAAa,KAAK;CACxC,GAAG;EAAC;EAAO;EAAU;CAAI,CAAC;CAE1B,MAAM,iBAAiB,WACnB,UACA,QAAQ,QAAQ,WAAW,OAAO,MAAM,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC;CAEvF,SAAS,QAAc;EACrB,SAAS,EAAE;EACX,aAAa,KAAK;CACpB;CAEA,SAAS,OAAO,OAAqB;EACnC,SAAS,KAAK;EAEd,IAAI,CAAC,UACH,MAAM;CAEV;CAEA,OACE,qBAAC,SAAD;EAAe;EAAM,eAAe,SAAU,OAAO,aAAa,IAAI,IAAI,MAAM;YAAhF,CACE,oBAAC,gBAAD;GAAgB,SAAA;aACd,oBAAC,UAAD;IAAQ,MAAK;IAAS,WAAW;IAAkB,GAAI;cACpD;GACK,CAAA;EACM,CAAA,GAEhB,qBAAC,gBAAD;GACE,OAAM;GACN,WAAW,GACT,8DACA,gBACF;aALF,CAOG,cACC,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,SAAD;KACE,cAAY,eAAe,EAAE,sBAAsB,gBAAgB;KACnE,aAAU;KACV,aAAW,SAAS,GAAG,OAAO,WAAW,KAAA;KACzC,WAAU;KACV,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;KAChD,aAAa;KACb,OAAO;IACR,CAAA,GACA,WACC,oBAAC,MAAD;KACE,MAAK;KACL,eAAY;KACZ,WAAU;IACX,CAAA,CAEA;OAGP,oBAAC,OAAD;IAAK,WAAU;IAA+B,MAAK;cAChD,eAAe,WAAW,IACzB,oBAAC,KAAD;KAAG,WAAU;eAAsC;IAAc,CAAA,IAEjE,eAAe,KAAK,WAAW;KAC7B,MAAM,aAAa,SAAS,SAAS,OAAO,KAAK;KAEjD,OACE,qBAAC,UAAD;MACE,iBAAe;MACf,WAAW,GACT,2JACA,cAAc,iBAChB;MACA,aAAU;MACV,aAAW,SAAS,GAAG,OAAO,UAAU,OAAO,UAAU,KAAA;MACzD,cAAY,OAAO;MAEnB,eAAe,OAAO,OAAO,KAAK;MAClC,MAAK;MACL,MAAK;gBAZP,CAcG,OAAO,OACP,cACC,oBAAC,MAAD;OAAM,MAAK;OAAQ,eAAY;OAAO,WAAU;MAA4B,CAAA,CAExE;QATD,OAAO,KASN;IAEZ,CAAC;GAEA,CAAA,CACS;IACT;;AAEb"}
1
+ {"version":3,"file":"combobox.js","names":[],"sources":["../../resources/js/ui/combobox.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport * as React from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { Option } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"./popover\";\n\nconst SEARCH_DEBOUNCE_MS = 250;\n\n/**\n * A popover select list with an optional search box and single/multi selection.\n *\n * Selection state is controlled by the consumer (`selected` + `onSelect`); the\n * consumer also owns option fetching. Pass `onSearch` for remote search (the\n * combobox debounces the query and renders `options` as given); omit it to\n * filter the provided `options` locally by label. The combobox closes itself\n * after a single-select. Pass `renderOption` to render rich option rows; the\n * option's label stays the accessible name. `onSelect` toggles selection\n * (used by dropdown-row clicks); tag-entry commits (Enter/comma/paste) that\n * match an existing option call `onCommit` instead, falling back to\n * `onSelect` when it is not provided, so consumers can make tag entry\n * additive instead of toggling.\n */\nfunction Combobox({\n contentClassName,\n creatable = false,\n emptyLabel,\n loading = false,\n multiple = false,\n onCommit,\n onCreate,\n onSearch,\n onSelect,\n open,\n onOpenChange,\n options,\n renderOption,\n searchLabel,\n searchPlaceholder,\n selected,\n showSearch = true,\n testId,\n trigger,\n triggerClassName,\n triggerProps,\n}: {\n contentClassName?: string;\n creatable?: boolean;\n emptyLabel?: string;\n loading?: boolean;\n multiple?: boolean;\n onCommit?: (value: string) => void;\n onCreate?: (label: string) => void;\n onSearch?: (query: string) => void;\n onSelect: (value: string) => void;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n options: Option[];\n renderOption?: (option: Option) => React.ReactNode;\n searchLabel?: string;\n searchPlaceholder?: string;\n selected: string[];\n showSearch?: boolean;\n testId?: string;\n trigger: React.ReactNode;\n triggerClassName?: string;\n triggerProps?: React.ComponentProps<\"button\"> & { \"data-test\"?: string };\n}) {\n const { t } = useT(\"lattice\");\n const [query, setQuery] = useState(\"\");\n\n function commitCreate(raw: string): void {\n const tokens = raw\n .split(\",\")\n .map((token) => token.trim())\n .filter(Boolean);\n\n for (const token of tokens) {\n const match = options.find((option) => option.label.toLowerCase() === token.toLowerCase());\n\n if (match) {\n (onCommit ?? onSelect)(match.value);\n } else {\n onCreate?.(token);\n }\n }\n\n if (multiple) {\n setQuery(\"\");\n } else {\n close();\n }\n }\n\n const exactMatch = options.some(\n (option) => option.label.toLowerCase() === query.trim().toLowerCase(),\n );\n\n useEffect(() => {\n if (!onSearch || !open) {\n return;\n }\n\n const timer = window.setTimeout(() => onSearch(query), SEARCH_DEBOUNCE_MS);\n\n return () => window.clearTimeout(timer);\n }, [query, onSearch, open]);\n\n const visibleOptions = onSearch\n ? options\n : options.filter((option) => option.label.toLowerCase().includes(query.toLowerCase()));\n\n function close(): void {\n setQuery(\"\");\n onOpenChange(false);\n }\n\n function choose(value: string): void {\n onSelect(value);\n\n if (!multiple) {\n close();\n }\n }\n\n return (\n <Popover open={open} onOpenChange={(next) => (next ? onOpenChange(true) : close())}>\n <PopoverTrigger asChild>\n <button type=\"button\" className={triggerClassName} {...triggerProps}>\n {trigger}\n </button>\n </PopoverTrigger>\n\n <PopoverContent\n align=\"start\"\n className={cn(\n \"w-[var(--radix-popover-trigger-width)] overflow-hidden p-0\",\n contentClassName,\n )}\n >\n {showSearch && (\n <div className=\"flex items-center gap-2 border-b border-lt-border px-3 py-2\">\n <input\n aria-label={searchLabel ?? t(\"form.search-options\", \"Search options\")}\n data-slot=\"combobox-search\"\n data-test={testId ? `${testId}-search` : undefined}\n className=\"w-full bg-transparent text-sm outline-none placeholder:text-lt-muted-fg\"\n onChange={(event) => {\n const next = event.target.value;\n\n if (creatable && next.includes(\",\")) {\n commitCreate(next);\n\n return;\n }\n\n setQuery(next);\n }}\n onKeyDown={(event) => {\n if (creatable && event.key === \"Enter\" && query.trim() !== \"\") {\n event.preventDefault();\n commitCreate(query);\n }\n }}\n placeholder={searchPlaceholder}\n value={query}\n />\n {loading && (\n <Icon\n name=\"loader-2\"\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0 animate-spin text-lt-muted-fg\"\n />\n )}\n </div>\n )}\n\n <div className=\"max-h-60 overflow-y-auto p-1\" role=\"listbox\">\n {creatable && query.trim() !== \"\" && !exactMatch && (\n <button\n className=\"flex w-full items-center gap-2 rounded-lt-sm px-3 py-1.5 text-left text-sm transition-colors hover:bg-lt-accent hover:text-lt-accent-fg\"\n data-test={testId ? `${testId}-create` : undefined}\n onClick={() => commitCreate(query)}\n type=\"button\"\n >\n <Icon name=\"plus\" aria-hidden=\"true\" className=\"size-lt-icon-md shrink-0\" />\n {t(\"form.create-option\", 'Create \"{{label}}\"', { label: query.trim() })}\n </button>\n )}\n\n {visibleOptions.length === 0 && !(creatable && query.trim() !== \"\" && !exactMatch) ? (\n <p className=\"px-3 py-2 text-sm text-lt-muted-fg\">{emptyLabel}</p>\n ) : (\n visibleOptions.map((option) => {\n const isSelected = selected.includes(option.value);\n\n return (\n <button\n aria-label={renderOption ? option.label : undefined}\n aria-selected={isSelected}\n className={cn(\n \"flex w-full items-center justify-between gap-2 rounded-lt-sm px-3 py-1.5 text-left text-sm transition-colors hover:bg-lt-accent hover:text-lt-accent-fg\",\n isSelected && \"bg-lt-accent/60\",\n )}\n data-slot=\"combobox-option\"\n data-test={testId ? `${testId}-option-${option.value}` : undefined}\n data-value={option.value}\n key={option.value}\n onClick={() => choose(option.value)}\n role=\"option\"\n type=\"button\"\n >\n {renderOption ? (\n <span className=\"flex min-w-0 flex-1 items-center gap-2\">\n {renderOption(option)}\n </span>\n ) : (\n option.label\n )}\n {isSelected && (\n <Icon name=\"check\" aria-hidden=\"true\" className=\"size-lt-icon-md shrink-0\" />\n )}\n </button>\n );\n })\n )}\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n\nexport { Combobox };\n"],"mappings":";;;;;;;AAQA,IAAM,qBAAqB;;;;;;;;;;;;;;;AAgB3B,SAAS,SAAS,EAChB,kBACA,YAAY,OACZ,YACA,UAAU,OACV,WAAW,OACX,UACA,UACA,UACA,UACA,MACA,cACA,SACA,cACA,aACA,mBACA,UACA,aAAa,MACb,QACA,SACA,kBACA,gBAuBC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CAErC,SAAS,aAAa,KAAmB;EACvC,MAAM,SAAS,IACZ,MAAM,GAAG,EACT,KAAK,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,OAAO;EAEjB,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,QAAQ,QAAQ,MAAM,WAAW,OAAO,MAAM,YAAY,MAAM,MAAM,YAAY,CAAC;GAEzF,IAAI,OACF,CAAC,YAAY,UAAU,MAAM,KAAK;QAElC,WAAW,KAAK;EAEpB;EAEA,IAAI,UACF,SAAS,EAAE;OAEX,MAAM;CAEV;CAEA,MAAM,aAAa,QAAQ,MACxB,WAAW,OAAO,MAAM,YAAY,MAAM,MAAM,KAAK,EAAE,YAAY,CACtE;CAEA,gBAAgB;EACd,IAAI,CAAC,YAAY,CAAC,MAChB;EAGF,MAAM,QAAQ,OAAO,iBAAiB,SAAS,KAAK,GAAG,kBAAkB;EAEzE,aAAa,OAAO,aAAa,KAAK;CACxC,GAAG;EAAC;EAAO;EAAU;CAAI,CAAC;CAE1B,MAAM,iBAAiB,WACnB,UACA,QAAQ,QAAQ,WAAW,OAAO,MAAM,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC,CAAC;CAEvF,SAAS,QAAc;EACrB,SAAS,EAAE;EACX,aAAa,KAAK;CACpB;CAEA,SAAS,OAAO,OAAqB;EACnC,SAAS,KAAK;EAEd,IAAI,CAAC,UACH,MAAM;CAEV;CAEA,OACE,qBAAC,SAAD;EAAe;EAAM,eAAe,SAAU,OAAO,aAAa,IAAI,IAAI,MAAM;YAAhF,CACE,oBAAC,gBAAD;GAAgB,SAAA;aACd,oBAAC,UAAD;IAAQ,MAAK;IAAS,WAAW;IAAkB,GAAI;cACpD;GACK,CAAA;EACM,CAAA,GAEhB,qBAAC,gBAAD;GACE,OAAM;GACN,WAAW,GACT,8DACA,gBACF;aALF,CAOG,cACC,qBAAC,OAAD;IAAK,WAAU;cAAf,CACE,oBAAC,SAAD;KACE,cAAY,eAAe,EAAE,uBAAuB,gBAAgB;KACpE,aAAU;KACV,aAAW,SAAS,GAAG,OAAO,WAAW,KAAA;KACzC,WAAU;KACV,WAAW,UAAU;MACnB,MAAM,OAAO,MAAM,OAAO;MAE1B,IAAI,aAAa,KAAK,SAAS,GAAG,GAAG;OACnC,aAAa,IAAI;OAEjB;MACF;MAEA,SAAS,IAAI;KACf;KACA,YAAY,UAAU;MACpB,IAAI,aAAa,MAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,IAAI;OAC7D,MAAM,eAAe;OACrB,aAAa,KAAK;MACpB;KACF;KACA,aAAa;KACb,OAAO;IACR,CAAA,GACA,WACC,oBAAC,MAAD;KACE,MAAK;KACL,eAAY;KACZ,WAAU;IACX,CAAA,CAEA;OAGP,qBAAC,OAAD;IAAK,WAAU;IAA+B,MAAK;cAAnD,CACG,aAAa,MAAM,KAAK,MAAM,MAAM,CAAC,cACpC,qBAAC,UAAD;KACE,WAAU;KACV,aAAW,SAAS,GAAG,OAAO,WAAW,KAAA;KACzC,eAAe,aAAa,KAAK;KACjC,MAAK;eAJP,CAME,oBAAC,MAAD;MAAM,MAAK;MAAO,eAAY;MAAO,WAAU;KAA4B,CAAA,GAC1E,EAAE,sBAAsB,wBAAsB,EAAE,OAAO,MAAM,KAAK,EAAE,CAAC,CAChE;QAGT,eAAe,WAAW,KAAK,EAAE,aAAa,MAAM,KAAK,MAAM,MAAM,CAAC,cACrE,oBAAC,KAAD;KAAG,WAAU;eAAsC;IAAc,CAAA,IAEjE,eAAe,KAAK,WAAW;KAC7B,MAAM,aAAa,SAAS,SAAS,OAAO,KAAK;KAEjD,OACE,qBAAC,UAAD;MACE,cAAY,eAAe,OAAO,QAAQ,KAAA;MAC1C,iBAAe;MACf,WAAW,GACT,2JACA,cAAc,iBAChB;MACA,aAAU;MACV,aAAW,SAAS,GAAG,OAAO,UAAU,OAAO,UAAU,KAAA;MACzD,cAAY,OAAO;MAEnB,eAAe,OAAO,OAAO,KAAK;MAClC,MAAK;MACL,MAAK;gBAbP,CAeG,eACC,oBAAC,QAAD;OAAM,WAAU;iBACb,aAAa,MAAM;MAChB,CAAA,IAEN,OAAO,OAER,cACC,oBAAC,MAAD;OAAM,MAAK;OAAQ,eAAY;OAAO,WAAU;MAA4B,CAAA,CAExE;QAfD,OAAO,KAeN;IAEZ,CAAC,CAEA;KACS;IACT;;AAEb"}
@@ -1,5 +1,12 @@
1
1
  import { ReactNode } from 'react';
2
2
  export declare function copyToClipboard(text: string): Promise<boolean>;
3
+ interface CopyButtonProps {
4
+ value: string;
5
+ label: string;
6
+ testId?: string;
7
+ className?: string;
8
+ }
9
+ export declare function CopyButton({ value, label, testId, className }: CopyButtonProps): ReactNode;
3
10
  interface CopyableTextProps {
4
11
  value: string;
5
12
  label: string;
@@ -1,3 +1,4 @@
1
+ import { cn } from "../lib/utils.js";
1
2
  import { Icon } from "../icons/sprite.js";
2
3
  import { useT } from "../i18n/instance.js";
3
4
  import { useEffect, useState } from "react";
@@ -12,7 +13,7 @@ async function copyToClipboard(text) {
12
13
  return false;
13
14
  }
14
15
  }
15
- function CopyableText({ value, label, testId, children }) {
16
+ function CopyButton({ value, label, testId, className }) {
16
17
  const { t } = useT("lattice");
17
18
  const [copied, setCopied] = useState(false);
18
19
  useEffect(() => {
@@ -24,23 +25,30 @@ function CopyableText({ value, label, testId, children }) {
24
25
  copyToClipboard(value);
25
26
  setCopied(true);
26
27
  }
27
- const ariaLabel = copied ? t("common.copiedValue", "Copied {{label}}", { label }) : t("common.copyValue", "Copy {{label}}", { label });
28
+ const ariaLabel = copied ? t("common.copied-value", "Copied {{label}}", { label }) : t("common.copy-value", "Copy {{label}}", { label });
29
+ return /* @__PURE__ */ jsxs("button", {
30
+ type: "button",
31
+ "data-test": testId,
32
+ className: cn("inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs", className),
33
+ "aria-label": ariaLabel,
34
+ onClick: handleCopy,
35
+ children: [/* @__PURE__ */ jsx(Icon, {
36
+ name: copied ? "check" : "copy",
37
+ className: "size-lt-icon-xs"
38
+ }), copied ? t("common.copied", "Copied") : t("common.copy", "Copy")]
39
+ });
40
+ }
41
+ function CopyableText({ value, label, testId, children }) {
28
42
  return /* @__PURE__ */ jsxs("div", {
29
43
  className: "inline-flex items-center gap-2",
30
- children: [children ?? value, /* @__PURE__ */ jsxs("button", {
31
- type: "button",
32
- "data-test": testId,
33
- className: "inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs",
34
- "aria-label": ariaLabel,
35
- onClick: handleCopy,
36
- children: [/* @__PURE__ */ jsx(Icon, {
37
- name: copied ? "check" : "copy",
38
- className: "size-lt-icon-xs"
39
- }), copied ? t("common.copied", "Copied") : t("common.copy", "Copy")]
44
+ children: [children ?? value, /* @__PURE__ */ jsx(CopyButton, {
45
+ value,
46
+ label,
47
+ testId
40
48
  })]
41
49
  });
42
50
  }
43
51
  //#endregion
44
- export { CopyableText, copyToClipboard };
52
+ export { CopyButton, CopyableText, copyToClipboard };
45
53
 
46
54
  //# sourceMappingURL=copyable-text.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"copyable-text.js","names":[],"sources":["../../resources/js/ui/copyable-text.tsx"],"sourcesContent":["import { useT } from \"@lattice-php/lattice/i18n\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { type ReactNode, useEffect, useState } from \"react\";\n\nexport async function copyToClipboard(text: string): Promise<boolean> {\n if (!navigator?.clipboard) {\n return false;\n }\n\n try {\n await navigator.clipboard.writeText(text);\n\n return true;\n } catch {\n return false;\n }\n}\n\ninterface CopyableTextProps {\n value: string;\n label: string;\n testId?: string;\n children?: ReactNode;\n}\n\nexport function CopyableText({ value, label, testId, children }: CopyableTextProps): ReactNode {\n const { t } = useT(\"lattice\");\n const [copied, setCopied] = useState(false);\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(value);\n setCopied(true);\n }\n\n const ariaLabel = copied\n ? t(\"common.copiedValue\", \"Copied {{label}}\", { label })\n : t(\"common.copyValue\", \"Copy {{label}}\", { label });\n\n return (\n <div className=\"inline-flex items-center gap-2\">\n {children ?? value}\n <button\n type=\"button\"\n data-test={testId}\n className=\"inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs\"\n aria-label={ariaLabel}\n onClick={handleCopy}\n >\n <Icon name={copied ? \"check\" : \"copy\"} className=\"size-lt-icon-xs\" />\n {copied ? t(\"common.copied\", \"Copied\") : t(\"common.copy\", \"Copy\")}\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;AAIA,eAAsB,gBAAgB,MAAgC;CACpE,IAAI,CAAC,WAAW,WACd,OAAO;CAGT,IAAI;EACF,MAAM,UAAU,UAAU,UAAU,IAAI;EAExC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AASA,SAAgB,aAAa,EAAE,OAAO,OAAO,QAAQ,YAA0C;CAC7F,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAE1C,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,KAAK;EAC1B,UAAU,IAAI;CAChB;CAEA,MAAM,YAAY,SACd,EAAE,sBAAsB,oBAAoB,EAAE,MAAM,CAAC,IACrD,EAAE,oBAAoB,kBAAkB,EAAE,MAAM,CAAC;CAErD,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACG,YAAY,OACb,qBAAC,UAAD;GACE,MAAK;GACL,aAAW;GACX,WAAU;GACV,cAAY;GACZ,SAAS;aALX,CAOE,oBAAC,MAAD;IAAM,MAAM,SAAS,UAAU;IAAQ,WAAU;GAAmB,CAAA,GACnE,SAAS,EAAE,iBAAiB,QAAQ,IAAI,EAAE,eAAe,MAAM,CAC1D;IACL;;AAET"}
1
+ {"version":3,"file":"copyable-text.js","names":[],"sources":["../../resources/js/ui/copyable-text.tsx"],"sourcesContent":["import { useT } from \"@lattice-php/lattice/i18n\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { type ReactNode, useEffect, useState } from \"react\";\n\nexport async function copyToClipboard(text: string): Promise<boolean> {\n if (!navigator?.clipboard) {\n return false;\n }\n\n try {\n await navigator.clipboard.writeText(text);\n\n return true;\n } catch {\n return false;\n }\n}\n\ninterface CopyButtonProps {\n value: string;\n label: string;\n testId?: string;\n className?: string;\n}\n\nexport function CopyButton({ value, label, testId, className }: CopyButtonProps): ReactNode {\n const { t } = useT(\"lattice\");\n const [copied, setCopied] = useState(false);\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(value);\n setCopied(true);\n }\n\n const ariaLabel = copied\n ? t(\"common.copied-value\", \"Copied {{label}}\", { label })\n : t(\"common.copy-value\", \"Copy {{label}}\", { label });\n\n return (\n <button\n type=\"button\"\n data-test={testId}\n className={cn(\n \"inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs\",\n className,\n )}\n aria-label={ariaLabel}\n onClick={handleCopy}\n >\n <Icon name={copied ? \"check\" : \"copy\"} className=\"size-lt-icon-xs\" />\n {copied ? t(\"common.copied\", \"Copied\") : t(\"common.copy\", \"Copy\")}\n </button>\n );\n}\n\ninterface CopyableTextProps {\n value: string;\n label: string;\n testId?: string;\n children?: ReactNode;\n}\n\nexport function CopyableText({ value, label, testId, children }: CopyableTextProps): ReactNode {\n return (\n <div className=\"inline-flex items-center gap-2\">\n {children ?? value}\n <CopyButton value={value} label={label} testId={testId} />\n </div>\n );\n}\n"],"mappings":";;;;;;AAKA,eAAsB,gBAAgB,MAAgC;CACpE,IAAI,CAAC,WAAW,WACd,OAAO;CAGT,IAAI;EACF,MAAM,UAAU,UAAU,UAAU,IAAI;EAExC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;AASA,SAAgB,WAAW,EAAE,OAAO,OAAO,QAAQ,aAAyC;CAC1F,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAE1C,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,KAAK;EAC1B,UAAU,IAAI;CAChB;CAEA,MAAM,YAAY,SACd,EAAE,uBAAuB,oBAAoB,EAAE,MAAM,CAAC,IACtD,EAAE,qBAAqB,kBAAkB,EAAE,MAAM,CAAC;CAEtD,OACE,qBAAC,UAAD;EACE,MAAK;EACL,aAAW;EACX,WAAW,GACT,0FACA,SACF;EACA,cAAY;EACZ,SAAS;YARX,CAUE,oBAAC,MAAD;GAAM,MAAM,SAAS,UAAU;GAAQ,WAAU;EAAmB,CAAA,GACnE,SAAS,EAAE,iBAAiB,QAAQ,IAAI,EAAE,eAAe,MAAM,CAC1D;;AAEZ;AASA,SAAgB,aAAa,EAAE,OAAO,OAAO,QAAQ,YAA0C;CAC7F,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACG,YAAY,OACb,oBAAC,YAAD;GAAmB;GAAc;GAAe;EAAS,CAAA,CACtD;;AAET"}
@@ -1,8 +1,8 @@
1
1
  import { LATTICE_EVENT } from "../events/event-names.js";
2
+ import { toNodes } from "../core/nodes.js";
2
3
  import { Renderer } from "../core/renderer.js";
3
4
  import { apiJson } from "../core/api.js";
4
5
  import { Skeleton } from "./skeleton.js";
5
- import { toNodes } from "../core/nodes.js";
6
6
  import { useCallback, useEffect, useState } from "react";
7
7
  import { jsx } from "react/jsx-runtime";
8
8
  //#region resources/js/ui/fragment.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"fragment.js","names":[],"sources":["../../resources/js/ui/fragment.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { apiJson } from \"@lattice-php/lattice/core/api\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { toNodes } from \"@lattice-php/lattice/core/nodes\";\nimport { LATTICE_EVENT, type ReloadComponentEvent } from \"@lattice-php/lattice/events/event-names\";\n\ntype FragmentResponse = {\n schema?: Schema;\n};\n\nconst fragmentSizeHeights = {\n lg: 320,\n md: 64,\n sm: 40,\n xl: 384,\n xs: 32,\n \"2xl\": 448,\n \"3xl\": 512,\n \"4xl\": 576,\n} as const;\n\nconst FragmentComponent: RendererComponent<\"fragment\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const isLazy = node.props.lazy === true;\n const componentRef = node.props.ref ?? \"\";\n const placeholderHeight = fragmentSizeHeights[node.props.size];\n const [components, setComponents] = useState(() => node.schema ?? []);\n const [hasLoaded, setHasLoaded] = useState(!isLazy);\n const [processing, setProcessing] = useState(isLazy && endpoint !== \"\");\n\n const load = useCallback(async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n setProcessing(true);\n\n try {\n const result = await apiJson<FragmentResponse>(endpoint, { ref: componentRef });\n\n setComponents(toNodes(result.schema));\n setHasLoaded(true);\n } finally {\n setProcessing(false);\n }\n }, [endpoint, componentRef]);\n\n useEffect(() => {\n if (!isLazy || hasLoaded) {\n return;\n }\n\n void load();\n }, [hasLoaded, isLazy, load]);\n\n useEffect(() => {\n function reload(event: Event): void {\n const detail = (event as ReloadComponentEvent).detail;\n\n if (detail?.component !== node.id) {\n return;\n }\n\n void load();\n }\n\n window.addEventListener(LATTICE_EVENT.reloadComponent, reload);\n\n return () => window.removeEventListener(LATTICE_EVENT.reloadComponent, reload);\n }, [load, node.id]);\n\n useEffect(() => {\n function reloadOnLocaleChange(): void {\n if (!hasLoaded) {\n return;\n }\n\n void load();\n }\n\n window.addEventListener(LATTICE_EVENT.localeChange, reloadOnLocaleChange);\n\n return () => window.removeEventListener(LATTICE_EVENT.localeChange, reloadOnLocaleChange);\n }, [hasLoaded, load]);\n\n return (\n <div\n data-lattice-fragment={node.id}\n style={processing && components.length === 0 ? { minHeight: placeholderHeight } : undefined}\n >\n {processing && components.length === 0 ? (\n <Skeleton className=\"w-full\" style={{ height: placeholderHeight }} />\n ) : (\n <Renderer nodes={components} />\n )}\n </div>\n );\n};\n\nexport default FragmentComponent;\n"],"mappings":";;;;;;;;AAYA,IAAM,sBAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,IAAM,qBAAoD,EAAE,WAAW;CACrE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,SAAS,KAAK,MAAM,SAAS;CACnC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,oBAAoB,oBAAoB,KAAK,MAAM;CACzD,MAAM,CAAC,YAAY,iBAAiB,eAAe,KAAK,UAAU,CAAC,CAAC;CACpE,MAAM,CAAC,WAAW,gBAAgB,SAAS,CAAC,MAAM;CAClD,MAAM,CAAC,YAAY,iBAAiB,SAAS,UAAU,aAAa,EAAE;CAEtE,MAAM,OAAO,YAAY,YAA2B;EAClD,IAAI,CAAC,UACH;EAGF,cAAc,IAAI;EAElB,IAAI;GAGF,cAAc,SAAQ,MAFD,QAA0B,UAAU,EAAE,KAAK,aAAa,CAAC,GAEjD,MAAM,CAAC;GACpC,aAAa,IAAI;EACnB,UAAU;GACR,cAAc,KAAK;EACrB;CACF,GAAG,CAAC,UAAU,YAAY,CAAC;CAE3B,gBAAgB;EACd,IAAI,CAAC,UAAU,WACb;EAGF,KAAU;CACZ,GAAG;EAAC;EAAW;EAAQ;CAAI,CAAC;CAE5B,gBAAgB;EACd,SAAS,OAAO,OAAoB;GAGlC,IAFgB,MAA+B,QAEnC,cAAc,KAAK,IAC7B;GAGF,KAAU;EACZ;EAEA,OAAO,iBAAiB,cAAc,iBAAiB,MAAM;EAE7D,aAAa,OAAO,oBAAoB,cAAc,iBAAiB,MAAM;CAC/E,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC;CAElB,gBAAgB;EACd,SAAS,uBAA6B;GACpC,IAAI,CAAC,WACH;GAGF,KAAU;EACZ;EAEA,OAAO,iBAAiB,cAAc,cAAc,oBAAoB;EAExE,aAAa,OAAO,oBAAoB,cAAc,cAAc,oBAAoB;CAC1F,GAAG,CAAC,WAAW,IAAI,CAAC;CAEpB,OACE,oBAAC,OAAD;EACE,yBAAuB,KAAK;EAC5B,OAAO,cAAc,WAAW,WAAW,IAAI,EAAE,WAAW,kBAAkB,IAAI,KAAA;YAEjF,cAAc,WAAW,WAAW,IACnC,oBAAC,UAAD;GAAU,WAAU;GAAS,OAAO,EAAE,QAAQ,kBAAkB;EAAI,CAAA,IAEpE,oBAAC,UAAD,EAAU,OAAO,WAAa,CAAA;CAE7B,CAAA;AAET"}
1
+ {"version":3,"file":"fragment.js","names":[],"sources":["../../resources/js/ui/fragment.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from \"react\";\nimport { apiJson } from \"@lattice-php/lattice/core/api\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport type { FragmentResponse } from \"@lattice-php/lattice/types/generated\";\nimport { toNodes } from \"@lattice-php/lattice/core/nodes\";\nimport { LATTICE_EVENT, type ReloadComponentEvent } from \"@lattice-php/lattice/events/event-names\";\n\nconst fragmentSizeHeights = {\n lg: 320,\n md: 64,\n sm: 40,\n xl: 384,\n xs: 32,\n \"2xl\": 448,\n \"3xl\": 512,\n \"4xl\": 576,\n} as const;\n\nconst FragmentComponent: RendererComponent<\"fragment\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const isLazy = node.props.lazy === true;\n const componentRef = node.props.ref ?? \"\";\n const placeholderHeight = fragmentSizeHeights[node.props.size];\n const [components, setComponents] = useState(() => node.schema ?? []);\n const [hasLoaded, setHasLoaded] = useState(!isLazy);\n const [processing, setProcessing] = useState(isLazy && endpoint !== \"\");\n\n const load = useCallback(async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n setProcessing(true);\n\n try {\n const result = await apiJson<FragmentResponse>(endpoint, { ref: componentRef });\n\n setComponents(toNodes(result.schema));\n setHasLoaded(true);\n } finally {\n setProcessing(false);\n }\n }, [endpoint, componentRef]);\n\n useEffect(() => {\n if (!isLazy || hasLoaded) {\n return;\n }\n\n void load();\n }, [hasLoaded, isLazy, load]);\n\n useEffect(() => {\n function reload(event: Event): void {\n const detail = (event as ReloadComponentEvent).detail;\n\n if (detail?.component !== node.id) {\n return;\n }\n\n void load();\n }\n\n window.addEventListener(LATTICE_EVENT.reloadComponent, reload);\n\n return () => window.removeEventListener(LATTICE_EVENT.reloadComponent, reload);\n }, [load, node.id]);\n\n useEffect(() => {\n function reloadOnLocaleChange(): void {\n if (!hasLoaded) {\n return;\n }\n\n void load();\n }\n\n window.addEventListener(LATTICE_EVENT.localeChange, reloadOnLocaleChange);\n\n return () => window.removeEventListener(LATTICE_EVENT.localeChange, reloadOnLocaleChange);\n }, [hasLoaded, load]);\n\n return (\n <div\n data-lattice-fragment={node.id}\n style={processing && components.length === 0 ? { minHeight: placeholderHeight } : undefined}\n >\n {processing && components.length === 0 ? (\n <Skeleton className=\"w-full\" style={{ height: placeholderHeight }} />\n ) : (\n <Renderer nodes={components} />\n )}\n </div>\n );\n};\n\nexport default FragmentComponent;\n"],"mappings":";;;;;;;;AASA,IAAM,sBAAsB;CAC1B,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,IAAM,qBAAoD,EAAE,WAAW;CACrE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,SAAS,KAAK,MAAM,SAAS;CACnC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,oBAAoB,oBAAoB,KAAK,MAAM;CACzD,MAAM,CAAC,YAAY,iBAAiB,eAAe,KAAK,UAAU,CAAC,CAAC;CACpE,MAAM,CAAC,WAAW,gBAAgB,SAAS,CAAC,MAAM;CAClD,MAAM,CAAC,YAAY,iBAAiB,SAAS,UAAU,aAAa,EAAE;CAEtE,MAAM,OAAO,YAAY,YAA2B;EAClD,IAAI,CAAC,UACH;EAGF,cAAc,IAAI;EAElB,IAAI;GAGF,cAAc,SAAQ,MAFD,QAA0B,UAAU,EAAE,KAAK,aAAa,CAAC,GAEjD,MAAM,CAAC;GACpC,aAAa,IAAI;EACnB,UAAU;GACR,cAAc,KAAK;EACrB;CACF,GAAG,CAAC,UAAU,YAAY,CAAC;CAE3B,gBAAgB;EACd,IAAI,CAAC,UAAU,WACb;EAGF,KAAU;CACZ,GAAG;EAAC;EAAW;EAAQ;CAAI,CAAC;CAE5B,gBAAgB;EACd,SAAS,OAAO,OAAoB;GAGlC,IAFgB,MAA+B,QAEnC,cAAc,KAAK,IAC7B;GAGF,KAAU;EACZ;EAEA,OAAO,iBAAiB,cAAc,iBAAiB,MAAM;EAE7D,aAAa,OAAO,oBAAoB,cAAc,iBAAiB,MAAM;CAC/E,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC;CAElB,gBAAgB;EACd,SAAS,uBAA6B;GACpC,IAAI,CAAC,WACH;GAGF,KAAU;EACZ;EAEA,OAAO,iBAAiB,cAAc,cAAc,oBAAoB;EAExE,aAAa,OAAO,oBAAoB,cAAc,cAAc,oBAAoB;CAC1F,GAAG,CAAC,WAAW,IAAI,CAAC;CAEpB,OACE,oBAAC,OAAD;EACE,yBAAuB,KAAK;EAC5B,OAAO,cAAc,WAAW,WAAW,IAAI,EAAE,WAAW,kBAAkB,IAAI,KAAA;YAEjF,cAAc,WAAW,WAAW,IACnC,oBAAC,UAAD;GAAU,WAAU;GAAS,OAAO,EAAE,QAAQ,kBAAkB;EAAI,CAAA,IAEpE,oBAAC,UAAD,EAAU,OAAO,WAAa,CAAA;CAE7B,CAAA;AAET"}
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { InfoTooltip } from "./info-tooltip.js";
3
+ import { CopyableText } from "./copyable-text.js";
3
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
5
  //#region resources/js/ui/heading.tsx
5
6
  var HeadingComponent = ({ node }) => {
@@ -7,32 +8,49 @@ var HeadingComponent = ({ node }) => {
7
8
  const level = Math.min(Math.max(node.props.level, 1), 6);
8
9
  const className = cn("max-w-3xl font-semibold tracking-normal text-balance text-lt-fg", level === 1 && "text-2xl font-bold leading-tight", level === 2 && "text-xl", level > 2 && "text-base");
9
10
  const content = /* @__PURE__ */ jsxs(Fragment, { children: [text, /* @__PURE__ */ jsx(InfoTooltip, { content: tooltip })] });
11
+ let heading;
10
12
  switch (level) {
11
- case 1: return /* @__PURE__ */ jsx("h1", {
12
- className,
13
- children: content
14
- });
15
- case 2: return /* @__PURE__ */ jsx("h2", {
16
- className,
17
- children: content
18
- });
19
- case 3: return /* @__PURE__ */ jsx("h3", {
20
- className,
21
- children: content
22
- });
23
- case 4: return /* @__PURE__ */ jsx("h4", {
24
- className,
25
- children: content
26
- });
27
- case 5: return /* @__PURE__ */ jsx("h5", {
28
- className,
29
- children: content
30
- });
31
- default: return /* @__PURE__ */ jsx("h6", {
13
+ case 1:
14
+ heading = /* @__PURE__ */ jsx("h1", {
15
+ className,
16
+ children: content
17
+ });
18
+ break;
19
+ case 2:
20
+ heading = /* @__PURE__ */ jsx("h2", {
21
+ className,
22
+ children: content
23
+ });
24
+ break;
25
+ case 3:
26
+ heading = /* @__PURE__ */ jsx("h3", {
27
+ className,
28
+ children: content
29
+ });
30
+ break;
31
+ case 4:
32
+ heading = /* @__PURE__ */ jsx("h4", {
33
+ className,
34
+ children: content
35
+ });
36
+ break;
37
+ case 5:
38
+ heading = /* @__PURE__ */ jsx("h5", {
39
+ className,
40
+ children: content
41
+ });
42
+ break;
43
+ default: heading = /* @__PURE__ */ jsx("h6", {
32
44
  className,
33
45
  children: content
34
46
  });
35
47
  }
48
+ if (!node.props.copyable) return heading;
49
+ return /* @__PURE__ */ jsx(CopyableText, {
50
+ value: text,
51
+ label: text,
52
+ children: heading
53
+ });
36
54
  };
37
55
  //#endregion
38
56
  export { HeadingComponent as default };
@@ -1 +1 @@
1
- {"version":3,"file":"heading.js","names":[],"sources":["../../resources/js/ui/heading.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { InfoTooltip } from \"./info-tooltip\";\n\nconst HeadingComponent: RendererComponent<\"heading\"> = ({ node }) => {\n const { text, tooltip } = node.props;\n const level = Math.min(Math.max(node.props.level, 1), 6);\n const className = cn(\n \"max-w-3xl font-semibold tracking-normal text-balance text-lt-fg\",\n level === 1 && \"text-2xl font-bold leading-tight\",\n level === 2 && \"text-xl\",\n level > 2 && \"text-base\",\n );\n\n const content = (\n <>\n {text}\n <InfoTooltip content={tooltip} />\n </>\n );\n\n switch (level) {\n case 1:\n return <h1 className={className}>{content}</h1>;\n case 2:\n return <h2 className={className}>{content}</h2>;\n case 3:\n return <h3 className={className}>{content}</h3>;\n case 4:\n return <h4 className={className}>{content}</h4>;\n case 5:\n return <h5 className={className}>{content}</h5>;\n default:\n return <h6 className={className}>{content}</h6>;\n }\n};\n\nexport default HeadingComponent;\n"],"mappings":";;;;AAIA,IAAM,oBAAkD,EAAE,WAAW;CACnE,MAAM,EAAE,MAAM,YAAY,KAAK;CAC/B,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,OAAO,CAAC,GAAG,CAAC;CACvD,MAAM,YAAY,GAChB,mEACA,UAAU,KAAK,oCACf,UAAU,KAAK,WACf,QAAQ,KAAK,WACf;CAEA,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,MACD,oBAAC,aAAD,EAAa,SAAS,QAAU,CAAA,CAChC,EAAA,CAAA;CAGJ,QAAQ,OAAR;EACE,KAAK,GACH,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;EAChD,KAAK,GACH,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;EAChD,KAAK,GACH,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;EAChD,KAAK,GACH,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;EAChD,KAAK,GACH,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;EAChD,SACE,OAAO,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;CAClD;AACF"}
1
+ {"version":3,"file":"heading.js","names":[],"sources":["../../resources/js/ui/heading.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { CopyableText } from \"./copyable-text\";\nimport { InfoTooltip } from \"./info-tooltip\";\n\nconst HeadingComponent: RendererComponent<\"heading\"> = ({ node }) => {\n const { text, tooltip } = node.props;\n const level = Math.min(Math.max(node.props.level, 1), 6);\n const className = cn(\n \"max-w-3xl font-semibold tracking-normal text-balance text-lt-fg\",\n level === 1 && \"text-2xl font-bold leading-tight\",\n level === 2 && \"text-xl\",\n level > 2 && \"text-base\",\n );\n\n const content = (\n <>\n {text}\n <InfoTooltip content={tooltip} />\n </>\n );\n\n let heading: ReactNode;\n\n switch (level) {\n case 1:\n heading = <h1 className={className}>{content}</h1>;\n break;\n case 2:\n heading = <h2 className={className}>{content}</h2>;\n break;\n case 3:\n heading = <h3 className={className}>{content}</h3>;\n break;\n case 4:\n heading = <h4 className={className}>{content}</h4>;\n break;\n case 5:\n heading = <h5 className={className}>{content}</h5>;\n break;\n default:\n heading = <h6 className={className}>{content}</h6>;\n }\n\n if (!node.props.copyable) {\n return heading;\n }\n\n return (\n <CopyableText value={text} label={text}>\n {heading}\n </CopyableText>\n );\n};\n\nexport default HeadingComponent;\n"],"mappings":";;;;;AAMA,IAAM,oBAAkD,EAAE,WAAW;CACnE,MAAM,EAAE,MAAM,YAAY,KAAK;CAC/B,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,OAAO,CAAC,GAAG,CAAC;CACvD,MAAM,YAAY,GAChB,mEACA,UAAU,KAAK,oCACf,UAAU,KAAK,WACf,QAAQ,KAAK,WACf;CAEA,MAAM,UACJ,qBAAA,UAAA,EAAA,UAAA,CACG,MACD,oBAAC,aAAD,EAAa,SAAS,QAAU,CAAA,CAChC,EAAA,CAAA;CAGJ,IAAI;CAEJ,QAAQ,OAAR;EACE,KAAK;GACH,UAAU,oBAAC,MAAD;IAAe;cAAY;GAAY,CAAA;GACjD;EACF,KAAK;GACH,UAAU,oBAAC,MAAD;IAAe;cAAY;GAAY,CAAA;GACjD;EACF,KAAK;GACH,UAAU,oBAAC,MAAD;IAAe;cAAY;GAAY,CAAA;GACjD;EACF,KAAK;GACH,UAAU,oBAAC,MAAD;IAAe;cAAY;GAAY,CAAA;GACjD;EACF,KAAK;GACH,UAAU,oBAAC,MAAD;IAAe;cAAY;GAAY,CAAA;GACjD;EACF,SACE,UAAU,oBAAC,MAAD;GAAe;aAAY;EAAY,CAAA;CACrD;CAEA,IAAI,CAAC,KAAK,MAAM,UACd,OAAO;CAGT,OACE,oBAAC,cAAD;EAAc,OAAO;EAAM,OAAO;YAC/B;CACW,CAAA;AAElB"}
@@ -0,0 +1,22 @@
1
+ import { VariantProps } from 'class-variance-authority';
2
+ import * as React from "react";
3
+ /**
4
+ * A compact, resizable icon button — the shared affordance behind toolbar
5
+ * triggers, popover triggers, and inline clear/remove buttons. `ghost` matches
6
+ * `Button variant="ghost"` (accent hover) and reacts to `aria-pressed` (toggle)
7
+ * and `data-[state=open]` (a popover trigger). `segmented` joins onto the right
8
+ * of an adjacent input. Size defaults to `sm`; pass `size` to resize.
9
+ */
10
+ declare const iconButtonVariants: (props?: ({
11
+ variant?: "ghost" | "segmented" | null | undefined;
12
+ size?: "xs" | "sm" | "md" | null | undefined;
13
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string;
14
+ export declare function IconButton({ icon, label, active, size, variant, className, children, ref, ...props }: Omit<React.ComponentProps<"button">, "aria-pressed"> & VariantProps<typeof iconButtonVariants> & {
15
+ /** Icon glyph name; omit and pass `children` for custom content. */
16
+ icon?: string;
17
+ /** Accessible name (also used when the button shows only an icon). */
18
+ label: string;
19
+ /** Toggle state — sets `aria-pressed` and the pressed styling. */
20
+ active?: boolean;
21
+ }): React.JSX.Element;
22
+ export { iconButtonVariants };
@@ -0,0 +1,51 @@
1
+ import { cn } from "../lib/utils.js";
2
+ import { Icon } from "../icons/sprite.js";
3
+ import "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { cva } from "class-variance-authority";
6
+ //#region resources/js/ui/icon-button.tsx
7
+ /**
8
+ * A compact, resizable icon button — the shared affordance behind toolbar
9
+ * triggers, popover triggers, and inline clear/remove buttons. `ghost` matches
10
+ * `Button variant="ghost"` (accent hover) and reacts to `aria-pressed` (toggle)
11
+ * and `data-[state=open]` (a popover trigger). `segmented` joins onto the right
12
+ * of an adjacent input. Size defaults to `sm`; pass `size` to resize.
13
+ */
14
+ var iconButtonVariants = cva(cn("relative inline-flex shrink-0 items-center justify-center rounded-lt-sm transition-colors", "outline-none focus-visible:ring-2 focus-visible:ring-lt-ring/50", "disabled:pointer-events-none disabled:opacity-50"), {
15
+ variants: {
16
+ variant: {
17
+ ghost: cn("text-lt-muted-fg hover:bg-lt-accent hover:text-lt-accent-fg", "data-[state=open]:bg-lt-accent data-[state=open]:text-lt-accent-fg", "aria-pressed:bg-lt-accent aria-pressed:text-lt-accent-fg"),
18
+ segmented: cn("-ml-px rounded-l-none border border-lt-input text-lt-muted-fg hover:bg-lt-accent hover:text-lt-accent-fg", "data-[state=open]:z-10 data-[state=open]:border-lt-primary data-[state=open]:text-lt-fg")
19
+ },
20
+ size: {
21
+ xs: "size-5 [&_svg]:size-lt-icon-sm",
22
+ sm: "size-7 [&_svg]:size-lt-icon-md",
23
+ md: "size-lt-control-md [&_svg]:size-lt-icon-md"
24
+ }
25
+ },
26
+ defaultVariants: {
27
+ variant: "ghost",
28
+ size: "sm"
29
+ }
30
+ });
31
+ function IconButton({ icon, label, active, size, variant, className, children, ref, ...props }) {
32
+ return /* @__PURE__ */ jsxs("button", {
33
+ ref,
34
+ type: "button",
35
+ "aria-label": label,
36
+ "aria-pressed": active,
37
+ className: cn(iconButtonVariants({
38
+ variant,
39
+ size
40
+ }), className),
41
+ ...props,
42
+ children: [icon ? /* @__PURE__ */ jsx(Icon, {
43
+ name: icon,
44
+ "aria-hidden": "true"
45
+ }) : null, children]
46
+ });
47
+ }
48
+ //#endregion
49
+ export { IconButton, iconButtonVariants };
50
+
51
+ //# sourceMappingURL=icon-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-button.js","names":[],"sources":["../../resources/js/ui/icon-button.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\n/**\n * A compact, resizable icon button — the shared affordance behind toolbar\n * triggers, popover triggers, and inline clear/remove buttons. `ghost` matches\n * `Button variant=\"ghost\"` (accent hover) and reacts to `aria-pressed` (toggle)\n * and `data-[state=open]` (a popover trigger). `segmented` joins onto the right\n * of an adjacent input. Size defaults to `sm`; pass `size` to resize.\n */\nconst iconButtonVariants = cva(\n cn(\n \"relative inline-flex shrink-0 items-center justify-center rounded-lt-sm transition-colors\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-lt-ring/50\",\n \"disabled:pointer-events-none disabled:opacity-50\",\n ),\n {\n variants: {\n variant: {\n ghost: cn(\n \"text-lt-muted-fg hover:bg-lt-accent hover:text-lt-accent-fg\",\n \"data-[state=open]:bg-lt-accent data-[state=open]:text-lt-accent-fg\",\n \"aria-pressed:bg-lt-accent aria-pressed:text-lt-accent-fg\",\n ),\n segmented: cn(\n \"-ml-px rounded-l-none border border-lt-input text-lt-muted-fg hover:bg-lt-accent hover:text-lt-accent-fg\",\n \"data-[state=open]:z-10 data-[state=open]:border-lt-primary data-[state=open]:text-lt-fg\",\n ),\n },\n size: {\n xs: \"size-5 [&_svg]:size-lt-icon-sm\",\n sm: \"size-7 [&_svg]:size-lt-icon-md\",\n md: \"size-lt-control-md [&_svg]:size-lt-icon-md\",\n },\n },\n defaultVariants: { variant: \"ghost\", size: \"sm\" },\n },\n);\n\nexport function IconButton({\n icon,\n label,\n active,\n size,\n variant,\n className,\n children,\n ref,\n ...props\n}: Omit<React.ComponentProps<\"button\">, \"aria-pressed\"> &\n VariantProps<typeof iconButtonVariants> & {\n /** Icon glyph name; omit and pass `children` for custom content. */\n icon?: string;\n /** Accessible name (also used when the button shows only an icon). */\n label: string;\n /** Toggle state — sets `aria-pressed` and the pressed styling. */\n active?: boolean;\n }) {\n return (\n <button\n ref={ref}\n type=\"button\"\n aria-label={label}\n aria-pressed={active}\n className={cn(iconButtonVariants({ variant, size }), className)}\n {...props}\n >\n {icon ? <Icon name={icon} aria-hidden=\"true\" /> : null}\n {children}\n </button>\n );\n}\n\nexport { iconButtonVariants };\n"],"mappings":";;;;;;;;;;;;;AAYA,IAAM,qBAAqB,IACzB,GACE,6FACA,mEACA,kDACF,GACA;CACE,UAAU;EACR,SAAS;GACP,OAAO,GACL,+DACA,sEACA,0DACF;GACA,WAAW,GACT,4GACA,yFACF;EACF;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;CACF;CACA,iBAAiB;EAAE,SAAS;EAAS,MAAM;CAAK;AAClD,CACF;AAEA,SAAgB,WAAW,EACzB,MACA,OACA,QACA,MACA,SACA,WACA,UACA,KACA,GAAG,SASA;CACH,OACE,qBAAC,UAAD;EACO;EACL,MAAK;EACL,cAAY;EACZ,gBAAc;EACd,WAAW,GAAG,mBAAmB;GAAE;GAAS;EAAK,CAAC,GAAG,SAAS;EAC9D,GAAI;YANN,CAQG,OAAO,oBAAC,MAAD;GAAM,MAAM;GAAM,eAAY;EAAQ,CAAA,IAAI,MACjD,QACK;;AAEZ"}
package/dist/ui/icon.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../lib/utils.js";
2
2
  import { IconRenderer } from "../icons/icon-renderer.js";
3
+ import { coerceColor, colorValue } from "../lib/color.js";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  //#region resources/js/ui/icon.tsx
5
6
  var sizeClass = {
@@ -12,20 +13,18 @@ var sizeClass = {
12
13
  "3xl": "size-lt-icon-3xl",
13
14
  "4xl": "size-lt-icon-4xl"
14
15
  };
15
- var colorClass = {
16
- default: "text-lt-fg",
17
- muted: "text-lt-muted-fg",
18
- primary: "text-lt-primary",
19
- success: "text-lt-success",
20
- info: "text-lt-info",
21
- warning: "text-lt-warning",
22
- danger: "text-lt-danger"
23
- };
24
16
  var IconComponent = ({ node }) => {
25
17
  const { name, size, color, class: className } = node.props;
26
- return /* @__PURE__ */ jsx(IconRenderer, {
18
+ const icon = /* @__PURE__ */ jsx(IconRenderer, {
27
19
  icon: name,
28
- className: cn(sizeClass[size], color ? colorClass[color] : void 0, className)
20
+ className: cn(sizeClass[size], className)
21
+ });
22
+ const coerced = coerceColor(color);
23
+ if (!coerced) return icon;
24
+ return /* @__PURE__ */ jsx("span", {
25
+ className: "contents",
26
+ style: { color: colorValue(coerced) },
27
+ children: icon
29
28
  });
30
29
  };
31
30
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"icon.js","names":[],"sources":["../../resources/js/ui/icon.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Color, Size } from \"@lattice-php/lattice/types/generated\";\n\nconst sizeClass: Record<Size, string> = {\n xs: \"size-lt-icon-xs\",\n sm: \"size-lt-icon-sm\",\n md: \"size-lt-icon-md\",\n lg: \"size-lt-icon-lg\",\n xl: \"size-lt-icon-xl\",\n \"2xl\": \"size-lt-icon-2xl\",\n \"3xl\": \"size-lt-icon-3xl\",\n \"4xl\": \"size-lt-icon-4xl\",\n};\n\nconst colorClass: Record<Color, string> = {\n default: \"text-lt-fg\",\n muted: \"text-lt-muted-fg\",\n primary: \"text-lt-primary\",\n success: \"text-lt-success\",\n info: \"text-lt-info\",\n warning: \"text-lt-warning\",\n danger: \"text-lt-danger\",\n};\n\nconst IconComponent: RendererComponent<\"icon\"> = ({ node }) => {\n const { name, size, color, class: className } = node.props;\n\n return (\n <IconRenderer\n icon={name}\n className={cn(sizeClass[size], color ? colorClass[color] : undefined, className)}\n />\n );\n};\n\nexport default IconComponent;\n"],"mappings":";;;;AAKA,IAAM,YAAkC;CACtC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,IAAM,aAAoC;CACxC,SAAS;CACT,OAAO;CACP,SAAS;CACT,SAAS;CACT,MAAM;CACN,SAAS;CACT,QAAQ;AACV;AAEA,IAAM,iBAA4C,EAAE,WAAW;CAC7D,MAAM,EAAE,MAAM,MAAM,OAAO,OAAO,cAAc,KAAK;CAErD,OACE,oBAAC,cAAD;EACE,MAAM;EACN,WAAW,GAAG,UAAU,OAAO,QAAQ,WAAW,SAAS,KAAA,GAAW,SAAS;CAChF,CAAA;AAEL"}
1
+ {"version":3,"file":"icon.js","names":[],"sources":["../../resources/js/ui/icon.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { coerceColor, colorValue } from \"@lattice-php/lattice/lib/color\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Size } from \"@lattice-php/lattice/types/generated\";\n\nconst sizeClass: Record<Size, string> = {\n xs: \"size-lt-icon-xs\",\n sm: \"size-lt-icon-sm\",\n md: \"size-lt-icon-md\",\n lg: \"size-lt-icon-lg\",\n xl: \"size-lt-icon-xl\",\n \"2xl\": \"size-lt-icon-2xl\",\n \"3xl\": \"size-lt-icon-3xl\",\n \"4xl\": \"size-lt-icon-4xl\",\n};\n\nconst IconComponent: RendererComponent<\"icon\"> = ({ node }) => {\n const { name, size, color, class: className } = node.props;\n const icon = <IconRenderer icon={name} className={cn(sizeClass[size], className)} />;\n const coerced = coerceColor(color);\n\n if (!coerced) {\n return icon;\n }\n\n return (\n <span className=\"contents\" style={{ color: colorValue(coerced) }}>\n {icon}\n </span>\n );\n};\n\nexport default IconComponent;\n"],"mappings":";;;;;AAMA,IAAM,YAAkC;CACtC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,IAAM,iBAA4C,EAAE,WAAW;CAC7D,MAAM,EAAE,MAAM,MAAM,OAAO,OAAO,cAAc,KAAK;CACrD,MAAM,OAAO,oBAAC,cAAD;EAAc,MAAM;EAAM,WAAW,GAAG,UAAU,OAAO,SAAS;CAAI,CAAA;CACnF,MAAM,UAAU,YAAY,KAAK;CAEjC,IAAI,CAAC,SACH,OAAO;CAGT,OACE,oBAAC,QAAD;EAAM,WAAU;EAAW,OAAO,EAAE,OAAO,WAAW,OAAO,EAAE;YAC5D;CACG,CAAA;AAEV"}
@@ -0,0 +1,12 @@
1
+ import { ReactNode } from 'react';
2
+ interface PreviewableImageProps {
3
+ src: string;
4
+ alt: string;
5
+ previewable: boolean;
6
+ width?: number;
7
+ height?: number;
8
+ className?: string;
9
+ testId?: string;
10
+ }
11
+ export declare function PreviewableImage({ src, alt, previewable, width, height, className, testId, }: PreviewableImageProps): ReactNode;
12
+ export {};