tempest-react-sdk 0.29.1 → 0.31.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 (85) hide show
  1. package/dist/components/Chat/Chat.cjs +2 -0
  2. package/dist/components/Chat/Chat.cjs.map +1 -0
  3. package/dist/components/Chat/Chat.js +126 -0
  4. package/dist/components/Chat/Chat.js.map +1 -0
  5. package/dist/components/Chat/Chat.module.cjs +2 -0
  6. package/dist/components/Chat/Chat.module.cjs.map +1 -0
  7. package/dist/components/Chat/Chat.module.js +32 -0
  8. package/dist/components/Chat/Chat.module.js.map +1 -0
  9. package/dist/components/Chat/ChatComposer.cjs +2 -0
  10. package/dist/components/Chat/ChatComposer.cjs.map +1 -0
  11. package/dist/components/Chat/ChatComposer.js +67 -0
  12. package/dist/components/Chat/ChatComposer.js.map +1 -0
  13. package/dist/components/Chat/chat-groups.cjs +2 -0
  14. package/dist/components/Chat/chat-groups.cjs.map +1 -0
  15. package/dist/components/Chat/chat-groups.js +89 -0
  16. package/dist/components/Chat/chat-groups.js.map +1 -0
  17. package/dist/components/FilterBar/FilterBar.cjs +2 -0
  18. package/dist/components/FilterBar/FilterBar.cjs.map +1 -0
  19. package/dist/components/FilterBar/FilterBar.js +238 -0
  20. package/dist/components/FilterBar/FilterBar.js.map +1 -0
  21. package/dist/components/FilterBar/FilterBar.module.cjs +2 -0
  22. package/dist/components/FilterBar/FilterBar.module.cjs.map +1 -0
  23. package/dist/components/FilterBar/FilterBar.module.js +20 -0
  24. package/dist/components/FilterBar/FilterBar.module.js.map +1 -0
  25. package/dist/components/FilterBar/filter-model.cjs +2 -0
  26. package/dist/components/FilterBar/filter-model.cjs.map +1 -0
  27. package/dist/components/FilterBar/filter-model.js +167 -0
  28. package/dist/components/FilterBar/filter-model.js.map +1 -0
  29. package/dist/components/Markdown/Markdown.cjs +2 -0
  30. package/dist/components/Markdown/Markdown.cjs.map +1 -0
  31. package/dist/components/Markdown/Markdown.js +124 -0
  32. package/dist/components/Markdown/Markdown.js.map +1 -0
  33. package/dist/components/Markdown/Markdown.module.cjs +2 -0
  34. package/dist/components/Markdown/Markdown.module.cjs.map +1 -0
  35. package/dist/components/Markdown/Markdown.module.js +23 -0
  36. package/dist/components/Markdown/Markdown.module.js.map +1 -0
  37. package/dist/components/Markdown/markdown-parse.cjs +7 -0
  38. package/dist/components/Markdown/markdown-parse.cjs.map +1 -0
  39. package/dist/components/Markdown/markdown-parse.js +269 -0
  40. package/dist/components/Markdown/markdown-parse.js.map +1 -0
  41. package/dist/components/Markdown/markdown-url.cjs +2 -0
  42. package/dist/components/Markdown/markdown-url.cjs.map +1 -0
  43. package/dist/components/Markdown/markdown-url.js +28 -0
  44. package/dist/components/Markdown/markdown-url.js.map +1 -0
  45. package/dist/components/Masonry/Masonry.cjs +2 -0
  46. package/dist/components/Masonry/Masonry.cjs.map +1 -0
  47. package/dist/components/Masonry/Masonry.js +70 -0
  48. package/dist/components/Masonry/Masonry.js.map +1 -0
  49. package/dist/components/Masonry/Masonry.module.cjs +2 -0
  50. package/dist/components/Masonry/Masonry.module.cjs.map +1 -0
  51. package/dist/components/Masonry/Masonry.module.js +10 -0
  52. package/dist/components/Masonry/Masonry.module.js.map +1 -0
  53. package/dist/components/Masonry/masonry-layout.cjs +2 -0
  54. package/dist/components/Masonry/masonry-layout.cjs.map +1 -0
  55. package/dist/components/Masonry/masonry-layout.js +20 -0
  56. package/dist/components/Masonry/masonry-layout.js.map +1 -0
  57. package/dist/components/Tour/Tour.cjs +2 -0
  58. package/dist/components/Tour/Tour.cjs.map +1 -0
  59. package/dist/components/Tour/Tour.js +186 -0
  60. package/dist/components/Tour/Tour.js.map +1 -0
  61. package/dist/components/Tour/Tour.module.cjs +2 -0
  62. package/dist/components/Tour/Tour.module.cjs.map +1 -0
  63. package/dist/components/Tour/Tour.module.js +21 -0
  64. package/dist/components/Tour/Tour.module.js.map +1 -0
  65. package/dist/components/Tour/tour-position.cjs +2 -0
  66. package/dist/components/Tour/tour-position.cjs.map +1 -0
  67. package/dist/components/Tour/tour-position.js +105 -0
  68. package/dist/components/Tour/tour-position.js.map +1 -0
  69. package/dist/components/Transfer/Transfer.cjs +2 -0
  70. package/dist/components/Transfer/Transfer.cjs.map +1 -0
  71. package/dist/components/Transfer/Transfer.js +163 -0
  72. package/dist/components/Transfer/Transfer.js.map +1 -0
  73. package/dist/components/Transfer/Transfer.module.cjs +2 -0
  74. package/dist/components/Transfer/Transfer.module.cjs.map +1 -0
  75. package/dist/components/Transfer/Transfer.module.js +19 -0
  76. package/dist/components/Transfer/Transfer.module.js.map +1 -0
  77. package/dist/components/Transfer/transfer-state.cjs +2 -0
  78. package/dist/components/Transfer/transfer-state.cjs.map +1 -0
  79. package/dist/components/Transfer/transfer-state.js +59 -0
  80. package/dist/components/Transfer/transfer-state.js.map +1 -0
  81. package/dist/styles.css +1 -1
  82. package/dist/tempest-react-sdk.cjs +1 -1
  83. package/dist/tempest-react-sdk.d.ts +674 -0
  84. package/dist/tempest-react-sdk.js +117 -105
  85. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":"gKAoCA,IAAM,EAA0C,CAAE,EAAG,EAAG,IAAK,EAAG,KAAM,CAAE,EAiBxE,SAAgB,EAAW,CACvB,QACA,WACA,UACA,UAAU,EACV,MACA,YACA,QACA,GAAG,GACa,CAChB,IAAM,GAAA,EAAA,EAAA,OAAA,CAAwC,IAAI,EAC5C,GAAA,EAAA,EAAA,OAAA,CAAyC,IAAI,GAAK,EAClD,CAAC,EAAO,IAAA,EAAA,EAAA,SAAA,CAAqB,CAAC,EAC9B,CAAC,EAAS,IAAA,EAAA,EAAA,SAAA,CAAiC,CAAC,CAAC,EAE7C,EAAc,EAAA,WAAW,EAAO,CAAO,GAU7C,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAQ,QACrB,GAAI,CAAC,GAAQ,OAAO,eAAmB,IAAa,OACpD,IAAM,EAAW,IAAI,gBAAgB,CAAC,KAAW,CAC7C,EAAS,EAAM,YAAY,KAAK,CACpC,CAAC,EAGD,OAFA,EAAS,QAAQ,CAAI,EACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,MAC9B,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAUL,IAAM,GAAA,EAAA,EAAA,YAAA,KAA4B,CAC9B,IAAM,EAAO,EAAM,KAAK,EAAG,IAAU,CACjC,IAAM,EAAO,EAAM,QAAQ,IAAI,CAAK,EACpC,OAAO,EAAO,EAAK,sBAAsB,CAAC,CAAC,OAAS,CACxD,CAAC,EACD,EAAY,GACR,EAAQ,SAAW,EAAK,QAAU,EAAQ,OAAO,EAAO,IAAM,IAAU,EAAK,EAAE,EACzE,EACA,CACV,CACJ,EAAG,CAAC,CAAK,CAAC,GAEV,EAAA,EAAA,gBAAA,CAAgB,EAAS,CAAC,EAAS,EAAa,CAAK,CAAC,GAOtD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,eAAmB,IAAa,OAC3C,IAAM,EAAW,IAAI,eAAe,CAAO,EAC3C,IAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,EAAG,EAAS,QAAQ,CAAI,EAChE,UAAa,EAAS,WAAW,CACrC,EAAG,CAAC,EAAS,CAAW,CAAC,EAEzB,IAAM,GAAA,EAAA,EAAA,QAAA,KAEE,EAAA,WACI,EAAM,KAAK,EAAG,IAAU,EAAQ,IAAU,CAAC,EAC3C,CACJ,EACJ,CAAC,EAAO,EAAS,CAAW,CAChC,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EACvC,MAAO,CAAE,GAAG,EAAO,GAAI,EAAM,CAAE,wBAAyB,CAAI,EAAI,CAAC,CAAG,EACpE,GAAI,WAEH,EAAO,KAAK,EAAa,KACtB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAkB,UAAW,EAAA,QAAO,gBAC/B,EAAY,IAAK,GAAU,CACxB,IAAM,EAAO,EAAM,GACnB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,UAAW,EAAA,QAAO,KAClB,IAAM,GAAS,CACP,EAAM,EAAM,QAAQ,IAAI,EAAO,CAAI,EAClC,EAAM,QAAQ,OAAO,CAAK,CACnC,WAEC,EAAS,EAAM,CAAK,CACpB,EARI,EAAU,EAAQ,EAAM,CAAK,EAAI,CAQrC,CAEb,CAAC,CACA,EAhBK,CAgBL,CACR,CACA,CAAA,CAEb"}
@@ -0,0 +1,70 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { columnsFor as t, distribute as n } from "./masonry-layout.js";
3
+ import r from "./Masonry.module.js";
4
+ import { useCallback as i, useEffect as a, useLayoutEffect as o, useMemo as s, useRef as c, useState as l } from "react";
5
+ import { jsx as u } from "react/jsx-runtime";
6
+ //#region src/components/Masonry/Masonry.tsx
7
+ var d = {
8
+ 0: 1,
9
+ 640: 2,
10
+ 1024: 3
11
+ };
12
+ function f({ items: f, children: p, itemKey: m, columns: h = d, gap: g, className: _, style: v, ...y }) {
13
+ let b = c(null), x = c(/* @__PURE__ */ new Map()), [S, C] = l(0), [w, T] = l([]), E = t(S, h);
14
+ a(() => {
15
+ let e = b.current;
16
+ if (!e || typeof ResizeObserver > "u") return;
17
+ let t = new ResizeObserver(([e]) => {
18
+ C(e.contentRect.width);
19
+ });
20
+ return t.observe(e), C(e.getBoundingClientRect().width), () => t.disconnect();
21
+ }, []);
22
+ let D = i(() => {
23
+ let e = f.map((e, t) => {
24
+ let n = x.current.get(t);
25
+ return n ? n.getBoundingClientRect().height : 1;
26
+ });
27
+ T((t) => t.length === e.length && t.every((t, n) => t === e[n]) ? t : e);
28
+ }, [f]);
29
+ o(D, [
30
+ D,
31
+ E,
32
+ S
33
+ ]), a(() => {
34
+ if (typeof ResizeObserver > "u") return;
35
+ let e = new ResizeObserver(D);
36
+ for (let t of x.current.values()) e.observe(t);
37
+ return () => e.disconnect();
38
+ }, [D, E]);
39
+ let O = s(() => n(f.map((e, t) => w[t] ?? 1), E), [
40
+ f,
41
+ w,
42
+ E
43
+ ]);
44
+ return /* @__PURE__ */ u("div", {
45
+ ref: b,
46
+ className: e(r.wrapper, _),
47
+ style: {
48
+ ...v,
49
+ ...g ? { "--tempest-masonry-gap": g } : {}
50
+ },
51
+ ...y,
52
+ children: O.map((e, t) => /* @__PURE__ */ u("div", {
53
+ className: r.column,
54
+ children: e.map((e) => {
55
+ let t = f[e];
56
+ return /* @__PURE__ */ u("div", {
57
+ className: r.item,
58
+ ref: (t) => {
59
+ t ? x.current.set(e, t) : x.current.delete(e);
60
+ },
61
+ children: p(t, e)
62
+ }, m ? m(t, e) : e);
63
+ })
64
+ }, t))
65
+ });
66
+ }
67
+ //#endregion
68
+ export { f as Masonry };
69
+
70
+ //# sourceMappingURL=Masonry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.js","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n /** What to lay out. */\n items: readonly T[];\n /** Render one card. */\n children: (item: T, index: number) => ReactNode;\n /** Stable key per item. Index is a fallback nobody should rely on. */\n itemKey?: (item: T, index: number) => string | number;\n /**\n * Column count: a fixed number, or a `width → columns` map read as\n * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n */\n columns?: number | Record<number, number>;\n /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n * {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n items,\n children,\n itemKey,\n columns = DEFAULT_COLUMNS,\n gap,\n className,\n style,\n ...rest\n}: MasonryProps<T>) {\n const wrapper = useRef<HTMLDivElement | null>(null);\n const cells = useRef<Map<number, HTMLElement>>(new Map());\n const [width, setWidth] = useState(0);\n const [heights, setHeights] = useState<number[]>([]);\n\n const columnCount = columnsFor(width, columns);\n\n /**\n * Track the container width, not the viewport.\n *\n * A masonry inside a drawer or a two-column page is narrower than the window,\n * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n * is what makes the breakpoint map mean \"this container\", which is the only\n * useful reading.\n */\n useEffect(() => {\n const node = wrapper.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n setWidth(entry.contentRect.width);\n });\n observer.observe(node);\n setWidth(node.getBoundingClientRect().width);\n return () => observer.disconnect();\n }, []);\n\n /**\n * Re-read every card's height, and store it only when something moved.\n *\n * The equality check is what stops the loop: writing a new array on every pass\n * would re-render, re-measure and write again. The first render lays out in\n * source order with weight 1 each — never blank — and this pass re-deals with\n * real numbers.\n */\n const measure = useCallback(() => {\n const next = items.map((_, index) => {\n const node = cells.current.get(index);\n return node ? node.getBoundingClientRect().height : 1;\n });\n setHeights((current) =>\n current.length === next.length && current.every((value, i) => value === next[i])\n ? current\n : next,\n );\n }, [items]);\n\n useLayoutEffect(measure, [measure, columnCount, width]);\n\n /**\n * Re-measure when a card changes size on its own — an image finishing its\n * download is the common one, and it is exactly the case a height measured at\n * mount gets wrong.\n */\n useEffect(() => {\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n for (const node of cells.current.values()) observer.observe(node);\n return () => observer.disconnect();\n }, [measure, columnCount]);\n\n const layout = useMemo(\n () =>\n distribute(\n items.map((_, index) => heights[index] ?? 1),\n columnCount,\n ),\n [items, heights, columnCount],\n );\n\n return (\n <div\n ref={wrapper}\n className={cn(styles.wrapper, className)}\n style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n {...rest}\n >\n {layout.map((columnItems, column) => (\n <div key={column} className={styles.column}>\n {columnItems.map((index) => {\n const item = items[index];\n return (\n <div\n key={itemKey ? itemKey(item, index) : index}\n className={styles.item}\n ref={(node) => {\n if (node) cells.current.set(index, node);\n else cells.current.delete(index);\n }}\n >\n {children(item, index)}\n </div>\n );\n })}\n </div>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;AAoCA,IAAM,IAA0C;CAAE,GAAG;CAAG,KAAK;CAAG,MAAM;AAAE;AAiBxE,SAAgB,EAAW,EACvB,UACA,aACA,YACA,aAAU,GACV,QACA,cACA,UACA,GAAG,KACa;CAChB,IAAM,IAAU,EAA8B,IAAI,GAC5C,IAAQ,kBAAiC,IAAI,IAAI,CAAC,GAClD,CAAC,GAAO,KAAY,EAAS,CAAC,GAC9B,CAAC,GAAS,KAAc,EAAmB,CAAC,CAAC,GAE7C,IAAc,EAAW,GAAO,CAAO;CAU7C,QAAgB;EACZ,IAAM,IAAO,EAAQ;EACrB,IAAI,CAAC,KAAQ,OAAO,iBAAmB,KAAa;EACpD,IAAM,IAAW,IAAI,gBAAgB,CAAC,OAAW;GAC7C,EAAS,EAAM,YAAY,KAAK;EACpC,CAAC;EAGD,OAFA,EAAS,QAAQ,CAAI,GACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,SAC9B,EAAS,WAAW;CACrC,GAAG,CAAC,CAAC;CAUL,IAAM,IAAU,QAAkB;EAC9B,IAAM,IAAO,EAAM,KAAK,GAAG,MAAU;GACjC,IAAM,IAAO,EAAM,QAAQ,IAAI,CAAK;GACpC,OAAO,IAAO,EAAK,sBAAsB,CAAC,CAAC,SAAS;EACxD,CAAC;EACD,GAAY,MACR,EAAQ,WAAW,EAAK,UAAU,EAAQ,OAAO,GAAO,MAAM,MAAU,EAAK,EAAE,IACzE,IACA,CACV;CACJ,GAAG,CAAC,CAAK,CAAC;CASV,AAPA,EAAgB,GAAS;EAAC;EAAS;EAAa;CAAK,CAAC,GAOtD,QAAgB;EACZ,IAAI,OAAO,iBAAmB,KAAa;EAC3C,IAAM,IAAW,IAAI,eAAe,CAAO;EAC3C,KAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,GAAG,EAAS,QAAQ,CAAI;EAChE,aAAa,EAAS,WAAW;CACrC,GAAG,CAAC,GAAS,CAAW,CAAC;CAEzB,IAAM,IAAS,QAEP,EACI,EAAM,KAAK,GAAG,MAAU,EAAQ,MAAU,CAAC,GAC3C,CACJ,GACJ;EAAC;EAAO;EAAS;CAAW,CAChC;CAEA,OACI,kBAAC,OAAD;EACI,KAAK;EACL,WAAW,EAAG,EAAO,SAAS,CAAS;EACvC,OAAO;GAAE,GAAG;GAAO,GAAI,IAAM,EAAE,yBAAyB,EAAI,IAAI,CAAC;EAAG;EACpE,GAAI;YAEH,EAAO,KAAK,GAAa,MACtB,kBAAC,OAAD;GAAkB,WAAW,EAAO;aAC/B,EAAY,KAAK,MAAU;IACxB,IAAM,IAAO,EAAM;IACnB,OACI,kBAAC,OAAD;KAEI,WAAW,EAAO;KAClB,MAAM,MAAS;MACX,AAAI,IAAM,EAAM,QAAQ,IAAI,GAAO,CAAI,IAClC,EAAM,QAAQ,OAAO,CAAK;KACnC;eAEC,EAAS,GAAM,CAAK;IACpB,GARI,IAAU,EAAQ,GAAM,CAAK,IAAI,CAQrC;GAEb,CAAC;EACA,GAhBK,CAgBL,CACR;CACA,CAAA;AAEb"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_wrapper_xO0g4`,t=`tempest_column_FX2W1`,n=`tempest_item_m7Agj`,r={wrapper:e,column:t,item:n};exports.column=t,exports.default=r,exports.item=n,exports.wrapper=e;
2
+ //# sourceMappingURL=Masonry.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.module.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
@@ -0,0 +1,10 @@
1
+ //#region src/components/Masonry/Masonry.module.css
2
+ var e = "tempest_wrapper_xO0g4", t = "tempest_column_FX2W1", n = "tempest_item_m7Agj", r = {
3
+ wrapper: e,
4
+ column: t,
5
+ item: n
6
+ };
7
+ //#endregion
8
+ export { t as column, r as default, n as item, e as wrapper };
9
+
10
+ //# sourceMappingURL=Masonry.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Masonry.module.js","names":[],"sources":["../../../src/components/Masonry/Masonry.module.css"],"sourcesContent":[".wrapper {\n display: flex;\n align-items: flex-start;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n width: 100%;\n}\n\n.column {\n display: flex;\n flex: 1 1 0;\n flex-direction: column;\n gap: var(--tempest-masonry-gap, var(--tempest-space-4));\n min-width: 0;\n}\n\n.item {\n min-width: 0;\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ function e(e,t){if(typeof t==`number`)return Math.max(1,Math.floor(t));let n=Object.keys(t).map(Number).filter(e=>Number.isFinite(e)).sort((e,t)=>e-t),r=1;for(let i of n)e>=i&&(r=t[i]);return Math.max(1,Math.floor(r))}function t(e,t){let n=Math.max(1,Math.floor(t)),r=Array.from({length:n},()=>[]),i=Array(n).fill(0);for(let t=0;t<e.length;t+=1){let a=0;for(let e=1;e<n;e+=1)i[e]<i[a]&&(a=e);r[a].push(t),i[a]+=Math.max(1,e[t]??1)}return r}exports.columnsFor=e,exports.distribute=t;
2
+ //# sourceMappingURL=masonry-layout.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masonry-layout.cjs","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":"AAYA,SAAgB,EAAW,EAAe,EAAkD,CACxF,GAAI,OAAO,GAAY,SAAU,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAO,CAAC,EACvE,IAAM,EAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,OAAQ,GAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,EAAG,IAAM,EAAI,CAAC,EACrB,EAAQ,EACZ,IAAK,IAAM,KAAQ,EACX,GAAS,IAAM,EAAQ,EAAQ,IAEvC,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,CAAK,CAAC,CACxC,CAkBA,SAAgB,EAAW,EAA4B,EAAiC,CACpF,IAAM,EAAQ,KAAK,IAAI,EAAG,KAAK,MAAM,CAAW,CAAC,EAC3C,EAAsB,MAAM,KAAK,CAAE,OAAQ,CAAM,MAAS,CAAC,CAAC,EAC5D,EAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC,EAE9C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAQ,OAAQ,GAAS,EAAG,CACpD,IAAI,EAAW,EACf,IAAK,IAAI,EAAS,EAAG,EAAS,EAAO,GAAU,EACvC,EAAO,GAAU,EAAO,KAAW,EAAW,GAEtD,EAAQ,EAAS,CAAC,KAAK,CAAK,EAC5B,EAAO,IAAa,KAAK,IAAI,EAAG,EAAQ,IAAU,CAAC,CACvD,CAEA,OAAO,CACX"}
@@ -0,0 +1,20 @@
1
+ //#region src/components/Masonry/masonry-layout.ts
2
+ function e(e, t) {
3
+ if (typeof t == "number") return Math.max(1, Math.floor(t));
4
+ let n = Object.keys(t).map(Number).filter((e) => Number.isFinite(e)).sort((e, t) => e - t), r = 1;
5
+ for (let i of n) e >= i && (r = t[i]);
6
+ return Math.max(1, Math.floor(r));
7
+ }
8
+ function t(e, t) {
9
+ let n = Math.max(1, Math.floor(t)), r = Array.from({ length: n }, () => []), i = Array(n).fill(0);
10
+ for (let t = 0; t < e.length; t += 1) {
11
+ let a = 0;
12
+ for (let e = 1; e < n; e += 1) i[e] < i[a] && (a = e);
13
+ r[a].push(t), i[a] += Math.max(1, e[t] ?? 1);
14
+ }
15
+ return r;
16
+ }
17
+ //#endregion
18
+ export { e as columnsFor, t as distribute };
19
+
20
+ //# sourceMappingURL=masonry-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"masonry-layout.js","names":[],"sources":["../../../src/components/Masonry/masonry-layout.ts"],"sourcesContent":["/**\n * Column count for a width, from a breakpoint map.\n *\n * The map is read as \"from this width up\", so `{ 0: 1, 640: 2, 1024: 3 }` means one\n * column on a phone and three on a desktop. Keys are sorted here rather than\n * trusted in insertion order — an object literal written out of order would\n * otherwise pick the wrong column count, silently.\n *\n * @param width - Container width in pixels.\n * @param columns - Breakpoint → column count, or a fixed number.\n * @returns At least 1, always.\n */\nexport function columnsFor(width: number, columns: number | Record<number, number>): number {\n if (typeof columns === \"number\") return Math.max(1, Math.floor(columns));\n const steps = Object.keys(columns)\n .map(Number)\n .filter((key) => Number.isFinite(key))\n .sort((a, b) => a - b);\n let count = 1;\n for (const step of steps) {\n if (width >= step) count = columns[step];\n }\n return Math.max(1, Math.floor(count));\n}\n\n/**\n * Deal items into columns, shortest column first.\n *\n * Round-robin (`index % columns`) is the obvious approach and produces ragged\n * columns the moment items differ in height — which is the only reason to reach\n * for a masonry layout at all. Feeding the shortest column keeps the bottom edge\n * as even as the content allows.\n *\n * Reading order is the cost, and it is why this is a layout for **independent**\n * cards: down a column rather than across the row. A list where item 2 must follow\n * item 1 wants a grid, not this.\n *\n * @param heights - Estimated or measured height per item, in the items' order.\n * @param columnCount - How many columns to fill.\n * @returns Item indexes per column.\n */\nexport function distribute(heights: readonly number[], columnCount: number): number[][] {\n const count = Math.max(1, Math.floor(columnCount));\n const columns: number[][] = Array.from({ length: count }, () => []);\n const totals = new Array<number>(count).fill(0);\n\n for (let index = 0; index < heights.length; index += 1) {\n let shortest = 0;\n for (let column = 1; column < count; column += 1) {\n if (totals[column] < totals[shortest]) shortest = column;\n }\n columns[shortest].push(index);\n totals[shortest] += Math.max(1, heights[index] ?? 1);\n }\n\n return columns;\n}\n"],"mappings":";AAYA,SAAgB,EAAW,GAAe,GAAkD;CACxF,IAAI,OAAO,KAAY,UAAU,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAO,CAAC;CACvE,IAAM,IAAQ,OAAO,KAAK,CAAO,CAAC,CAC7B,IAAI,MAAM,CAAC,CACX,QAAQ,MAAQ,OAAO,SAAS,CAAG,CAAC,CAAC,CACrC,MAAM,GAAG,MAAM,IAAI,CAAC,GACrB,IAAQ;CACZ,KAAK,IAAM,KAAQ,GACf,AAAI,KAAS,MAAM,IAAQ,EAAQ;CAEvC,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,CAAK,CAAC;AACxC;AAkBA,SAAgB,EAAW,GAA4B,GAAiC;CACpF,IAAM,IAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,CAAW,CAAC,GAC3C,IAAsB,MAAM,KAAK,EAAE,QAAQ,EAAM,SAAS,CAAC,CAAC,GAC5D,IAAa,MAAc,CAAK,CAAC,CAAC,KAAK,CAAC;CAE9C,KAAK,IAAI,IAAQ,GAAG,IAAQ,EAAQ,QAAQ,KAAS,GAAG;EACpD,IAAI,IAAW;EACf,KAAK,IAAI,IAAS,GAAG,IAAS,GAAO,KAAU,GAC3C,AAAI,EAAO,KAAU,EAAO,OAAW,IAAW;EAGtD,AADA,EAAQ,EAAS,CAAC,KAAK,CAAK,GAC5B,EAAO,MAAa,KAAK,IAAI,GAAG,EAAQ,MAAU,CAAC;CACvD;CAEA,OAAO;AACX"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../utils/cn.cjs"),t=require("../../hooks/use-focus-trap.cjs"),n=require("./tour-position.cjs"),r=require("./Tour.module.cjs");let i=require("react"),a=require("react/jsx-runtime");var o={next:`Próximo`,back:`Voltar`,finish:`Concluir`,skip:`Pular`,close:`Fechar tour`,progress:(e,t)=>`Passo ${e} de ${t}`},s={next:`Next`,back:`Back`,finish:`Done`,skip:`Skip`,close:`Close tour`,progress:(e,t)=>`Step ${e} of ${t}`};function c({steps:c,open:l,onClose:u,onFinish:d,index:f,onIndexChange:p,locale:m=`pt-BR`,spotlightPadding:h=4,className:g}){let _=m===`en`?s:o,[v,y]=(0,i.useState)(0),b=Math.min(f??v,Math.max(0,c.length-1)),x=c[b],S=(0,i.useRef)(null),[C,w]=(0,i.useState)(null),[T,E]=(0,i.useState)({placement:`center`,top:0,left:0}),[D,O]=(0,i.useState)({width:0,height:0}),k=(0,i.useId)(),A=(0,i.useId)();t.useFocusTrap(S,l);let j=(0,i.useCallback)(e=>{f===void 0&&y(e),p?.(e)},[f,p]);(0,i.useEffect)(()=>{l&&f===void 0&&y(0)},[l,f]);let M=(0,i.useCallback)(()=>{if(!l||!x)return;let e=(x.target?document.querySelector(x.target):null)?.getBoundingClientRect()??null;w(e?{top:e.top,left:e.left,width:e.width,height:e.height}:null);let t={width:S.current?.offsetWidth??320,height:S.current?.offsetHeight??160},r={width:window.innerWidth,height:window.innerHeight};O(r),E(n.placeCard({target:e?{top:e.top,left:e.left,width:e.width,height:e.height}:null,card:t,viewport:r,preferred:x.placement}))},[l,x]);if((0,i.useLayoutEffect)(()=>{if(!l||!x)return;(x.target?document.querySelector(x.target):null)?.scrollIntoView({block:`center`,inline:`nearest`,behavior:`smooth`});let e=requestAnimationFrame(M);return()=>cancelAnimationFrame(e)},[l,x,M]),(0,i.useEffect)(()=>{if(l)return window.addEventListener(`resize`,M),window.addEventListener(`scroll`,M,!0),()=>{window.removeEventListener(`resize`,M),window.removeEventListener(`scroll`,M,!0)}},[l,M]),(0,i.useEffect)(()=>{l&&S.current?.focus()},[l,b]),!l||!x)return null;let N=b===c.length-1,P=()=>{d?.(),u()};return(0,a.jsxs)(`div`,{className:r.default.root,"data-placement":T.placement,children:[n.backdropRects(C,D,h).map((e,t)=>(0,a.jsx)(`div`,{className:r.default.backdrop,style:{top:e.top,left:e.left,width:e.width,height:e.height},onClick:u},t)),C&&(0,a.jsx)(`div`,{className:r.default.spotlight,style:{top:C.top-h,left:C.left-h,width:C.width+h*2,height:C.height+h*2}}),(0,a.jsxs)(`div`,{ref:S,className:e.cn(r.default.card,g),style:{top:T.top,left:T.left},role:`dialog`,"aria-modal":`true`,"aria-labelledby":x.title?k:void 0,"aria-describedby":A,tabIndex:-1,onKeyDown:e=>{if(e.key===`Escape`){e.stopPropagation(),u();return}e.key===`ArrowRight`&&!N&&(e.preventDefault(),j(b+1)),e.key===`ArrowLeft`&&b>0&&(e.preventDefault(),j(b-1))},children:[(0,a.jsxs)(`div`,{className:r.default.header,children:[(0,a.jsx)(`span`,{className:r.default.progress,children:_.progress(b+1,c.length)}),(0,a.jsx)(`button`,{type:`button`,className:r.default.close,"aria-label":_.close,onClick:u,children:`×`})]}),x.title&&(0,a.jsx)(`h2`,{className:r.default.title,id:k,children:x.title}),(0,a.jsx)(`div`,{className:r.default.body,id:A,children:x.body}),(0,a.jsxs)(`div`,{className:r.default.footer,children:[!N&&(0,a.jsx)(`button`,{type:`button`,className:r.default.skip,onClick:u,children:_.skip}),(0,a.jsxs)(`div`,{className:r.default.actions,children:[b>0&&(0,a.jsx)(`button`,{type:`button`,className:r.default.secondary,onClick:()=>j(b-1),children:_.back}),(0,a.jsx)(`button`,{type:`button`,className:r.default.primary,onClick:()=>N?P():j(b+1),children:N?_.finish:_.next})]})]})]})]})}exports.Tour=c;
2
+ //# sourceMappingURL=Tour.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tour.cjs","names":[],"sources":["../../../src/components/Tour/Tour.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { cn } from \"@/utils/cn\";\n\nimport { backdropRects, placeCard, type TourPlacement, type TourRect } from \"./tour-position\";\nimport styles from \"./Tour.module.css\";\n\n/** One stop of the tour. */\nexport interface TourStep {\n /**\n * CSS selector of the element to point at.\n *\n * A selector rather than a ref, so a tour can be declared as data — in a config\n * file, from the backend, next to the copy — without every screen having to\n * thread refs up to whoever renders the tour.\n */\n target?: string;\n /** Heading of the card. */\n title?: ReactNode;\n /** The explanation. */\n body: ReactNode;\n /** Preferred side. Default `\"bottom\"`, flipped when it does not fit. */\n placement?: TourPlacement;\n}\n\n/** Labels, per locale. */\ninterface TourStrings {\n next: string;\n back: string;\n finish: string;\n skip: string;\n close: string;\n progress: (current: number, total: number) => string;\n}\n\nconst PT_BR: TourStrings = {\n next: \"Próximo\",\n back: \"Voltar\",\n finish: \"Concluir\",\n skip: \"Pular\",\n close: \"Fechar tour\",\n progress: (current, total) => `Passo ${current} de ${total}`,\n};\n\nconst EN: TourStrings = {\n next: \"Next\",\n back: \"Back\",\n finish: \"Done\",\n skip: \"Skip\",\n close: \"Close tour\",\n progress: (current, total) => `Step ${current} of ${total}`,\n};\n\nexport interface TourProps {\n /** The stops, in order. */\n steps: readonly TourStep[];\n /** Whether the tour is showing. Controlled. */\n open: boolean;\n /** Closed by `Esc`, by the close button or by \"skip\". */\n onClose: () => void;\n /** Called after the last step is confirmed. `onClose` follows it. */\n onFinish?: () => void;\n /** Current step index, when the app wants to drive it. */\n index?: number;\n /** Step changed — required only when `index` is given. */\n onIndexChange?: (index: number) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Space kept clear around the highlighted element, in px. Default 4. */\n spotlightPadding?: number;\n /** Extra class on the card. */\n className?: string;\n}\n\n/**\n * A guided tour: dim the page, highlight one element at a time, explain it.\n *\n * The highlighted element stays **clickable** while everything else is blocked,\n * because the useful kind of coachmark says \"press this\" and lets you press it. That\n * is why the backdrop is four rectangles around the target rather than one overlay\n * with a `box-shadow` hole — a shadow is not hit-testable, so a hole made that way\n * would not block anything.\n *\n * Whether a user has seen the tour is the app's business: this component takes\n * `open` and emits `onClose`/`onFinish`. Persisting a flag in `localStorage` is one\n * line in the app and a wrong default here.\n *\n * @example\n * const [open, setOpen] = useState(!storage.get(\"tour-v1\"));\n *\n * <Tour\n * open={open}\n * steps={[\n * { target: \"#novo-pedido\", title: \"Comece aqui\", body: \"Todo pedido nasce deste botão.\" },\n * { target: \"[data-tour='filtros']\", body: \"E filtre por período aqui.\" },\n * ]}\n * onClose={() => setOpen(false)}\n * onFinish={() => storage.set(\"tour-v1\", true)}\n * />\n */\nexport function Tour({\n steps,\n open,\n onClose,\n onFinish,\n index,\n onIndexChange,\n locale = \"pt-BR\",\n spotlightPadding = 4,\n className,\n}: TourProps) {\n const strings = locale === \"en\" ? EN : PT_BR;\n const [internal, setInternal] = useState(0);\n const current = Math.min(index ?? internal, Math.max(0, steps.length - 1));\n const step = steps[current];\n\n const card = useRef<HTMLDivElement | null>(null);\n const [target, setTarget] = useState<TourRect | null>(null);\n const [position, setPosition] = useState<{\n placement: TourPlacement;\n top: number;\n left: number;\n }>({ placement: \"center\", top: 0, left: 0 });\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n const titleId = useId();\n const bodyId = useId();\n\n useFocusTrap(card, open);\n\n const goTo = useCallback(\n (next: number) => {\n if (index === undefined) setInternal(next);\n onIndexChange?.(next);\n },\n [index, onIndexChange],\n );\n\n /** Reset to the first step whenever the tour opens again. */\n useEffect(() => {\n if (open && index === undefined) setInternal(0);\n }, [open, index]);\n\n /**\n * Find the step's element, bring it into view, and measure everything.\n *\n * The scroll happens before measuring, and the measurement is deferred to the\n * next frame: reading a rect in the same tick as `scrollIntoView` gives the\n * position the element had *before* the scroll, which lands the card over the\n * wrong part of the page.\n */\n const measure = useCallback(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n const rect = element?.getBoundingClientRect() ?? null;\n setTarget(\n rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n );\n\n const size = {\n width: card.current?.offsetWidth ?? 320,\n height: card.current?.offsetHeight ?? 160,\n };\n const view = { width: window.innerWidth, height: window.innerHeight };\n setViewport(view);\n setPosition(\n placeCard({\n target: rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n card: size,\n viewport: view,\n preferred: step.placement,\n }),\n );\n }, [open, step]);\n\n useLayoutEffect(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n element?.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior: \"smooth\" });\n const frame = requestAnimationFrame(measure);\n return () => cancelAnimationFrame(frame);\n }, [open, step, measure]);\n\n useEffect(() => {\n if (!open) return;\n window.addEventListener(\"resize\", measure);\n window.addEventListener(\"scroll\", measure, true);\n return () => {\n window.removeEventListener(\"resize\", measure);\n window.removeEventListener(\"scroll\", measure, true);\n };\n }, [open, measure]);\n\n useEffect(() => {\n if (open) card.current?.focus();\n }, [open, current]);\n\n if (!open || !step) return null;\n\n const last = current === steps.length - 1;\n\n const finish = (): void => {\n onFinish?.();\n onClose();\n };\n\n /**\n * Arrow keys walk the tour and `Esc` leaves it.\n *\n * `Esc` is handled here rather than on `window` so a tour opened over a modal\n * does not close both: the innermost handler that sees the key wins, and the\n * card has focus.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n return;\n }\n if (event.key === \"ArrowRight\" && !last) {\n event.preventDefault();\n goTo(current + 1);\n }\n if (event.key === \"ArrowLeft\" && current > 0) {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div className={styles.root} data-placement={position.placement}>\n {backdropRects(target, viewport, spotlightPadding).map((rect, i) => (\n <div\n key={i}\n className={styles.backdrop}\n style={{\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n }}\n onClick={onClose}\n />\n ))}\n\n {target && (\n <div\n className={styles.spotlight}\n style={{\n top: target.top - spotlightPadding,\n left: target.left - spotlightPadding,\n width: target.width + spotlightPadding * 2,\n height: target.height + spotlightPadding * 2,\n }}\n />\n )}\n\n <div\n ref={card}\n className={cn(styles.card, className)}\n style={{ top: position.top, left: position.left }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={step.title ? titleId : undefined}\n aria-describedby={bodyId}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n >\n <div className={styles.header}>\n <span className={styles.progress}>\n {strings.progress(current + 1, steps.length)}\n </span>\n <button\n type=\"button\"\n className={styles.close}\n aria-label={strings.close}\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {step.title && (\n <h2 className={styles.title} id={titleId}>\n {step.title}\n </h2>\n )}\n <div className={styles.body} id={bodyId}>\n {step.body}\n </div>\n\n <div className={styles.footer}>\n {!last && (\n <button type=\"button\" className={styles.skip} onClick={onClose}>\n {strings.skip}\n </button>\n )}\n <div className={styles.actions}>\n {current > 0 && (\n <button\n type=\"button\"\n className={styles.secondary}\n onClick={() => goTo(current - 1)}\n >\n {strings.back}\n </button>\n )}\n <button\n type=\"button\"\n className={styles.primary}\n onClick={() => (last ? finish() : goTo(current + 1))}\n >\n {last ? strings.finish : strings.next}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,186 @@
1
+ import { cn as e } from "../../utils/cn.js";
2
+ import { useFocusTrap as t } from "../../hooks/use-focus-trap.js";
3
+ import { backdropRects as n, placeCard as r } from "./tour-position.js";
4
+ import i from "./Tour.module.js";
5
+ import { useCallback as a, useEffect as o, useId as s, useLayoutEffect as c, useRef as l, useState as u } from "react";
6
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
7
+ //#region src/components/Tour/Tour.tsx
8
+ var p = {
9
+ next: "Próximo",
10
+ back: "Voltar",
11
+ finish: "Concluir",
12
+ skip: "Pular",
13
+ close: "Fechar tour",
14
+ progress: (e, t) => `Passo ${e} de ${t}`
15
+ }, m = {
16
+ next: "Next",
17
+ back: "Back",
18
+ finish: "Done",
19
+ skip: "Skip",
20
+ close: "Close tour",
21
+ progress: (e, t) => `Step ${e} of ${t}`
22
+ };
23
+ function h({ steps: h, open: g, onClose: _, onFinish: v, index: y, onIndexChange: b, locale: x = "pt-BR", spotlightPadding: S = 4, className: C }) {
24
+ let w = x === "en" ? m : p, [T, E] = u(0), D = Math.min(y ?? T, Math.max(0, h.length - 1)), O = h[D], k = l(null), [A, j] = u(null), [M, N] = u({
25
+ placement: "center",
26
+ top: 0,
27
+ left: 0
28
+ }), [P, F] = u({
29
+ width: 0,
30
+ height: 0
31
+ }), I = s(), L = s();
32
+ t(k, g);
33
+ let R = a((e) => {
34
+ y === void 0 && E(e), b?.(e);
35
+ }, [y, b]);
36
+ o(() => {
37
+ g && y === void 0 && E(0);
38
+ }, [g, y]);
39
+ let z = a(() => {
40
+ if (!g || !O) return;
41
+ let e = (O.target ? document.querySelector(O.target) : null)?.getBoundingClientRect() ?? null;
42
+ j(e ? {
43
+ top: e.top,
44
+ left: e.left,
45
+ width: e.width,
46
+ height: e.height
47
+ } : null);
48
+ let t = {
49
+ width: k.current?.offsetWidth ?? 320,
50
+ height: k.current?.offsetHeight ?? 160
51
+ }, n = {
52
+ width: window.innerWidth,
53
+ height: window.innerHeight
54
+ };
55
+ F(n), N(r({
56
+ target: e ? {
57
+ top: e.top,
58
+ left: e.left,
59
+ width: e.width,
60
+ height: e.height
61
+ } : null,
62
+ card: t,
63
+ viewport: n,
64
+ preferred: O.placement
65
+ }));
66
+ }, [g, O]);
67
+ if (c(() => {
68
+ if (!g || !O) return;
69
+ (O.target ? document.querySelector(O.target) : null)?.scrollIntoView({
70
+ block: "center",
71
+ inline: "nearest",
72
+ behavior: "smooth"
73
+ });
74
+ let e = requestAnimationFrame(z);
75
+ return () => cancelAnimationFrame(e);
76
+ }, [
77
+ g,
78
+ O,
79
+ z
80
+ ]), o(() => {
81
+ if (g) return window.addEventListener("resize", z), window.addEventListener("scroll", z, !0), () => {
82
+ window.removeEventListener("resize", z), window.removeEventListener("scroll", z, !0);
83
+ };
84
+ }, [g, z]), o(() => {
85
+ g && k.current?.focus();
86
+ }, [g, D]), !g || !O) return null;
87
+ let B = D === h.length - 1, V = () => {
88
+ v?.(), _();
89
+ };
90
+ return /* @__PURE__ */ f("div", {
91
+ className: i.root,
92
+ "data-placement": M.placement,
93
+ children: [
94
+ n(A, P, S).map((e, t) => /* @__PURE__ */ d("div", {
95
+ className: i.backdrop,
96
+ style: {
97
+ top: e.top,
98
+ left: e.left,
99
+ width: e.width,
100
+ height: e.height
101
+ },
102
+ onClick: _
103
+ }, t)),
104
+ A && /* @__PURE__ */ d("div", {
105
+ className: i.spotlight,
106
+ style: {
107
+ top: A.top - S,
108
+ left: A.left - S,
109
+ width: A.width + S * 2,
110
+ height: A.height + S * 2
111
+ }
112
+ }),
113
+ /* @__PURE__ */ f("div", {
114
+ ref: k,
115
+ className: e(i.card, C),
116
+ style: {
117
+ top: M.top,
118
+ left: M.left
119
+ },
120
+ role: "dialog",
121
+ "aria-modal": "true",
122
+ "aria-labelledby": O.title ? I : void 0,
123
+ "aria-describedby": L,
124
+ tabIndex: -1,
125
+ onKeyDown: (e) => {
126
+ if (e.key === "Escape") {
127
+ e.stopPropagation(), _();
128
+ return;
129
+ }
130
+ e.key === "ArrowRight" && !B && (e.preventDefault(), R(D + 1)), e.key === "ArrowLeft" && D > 0 && (e.preventDefault(), R(D - 1));
131
+ },
132
+ children: [
133
+ /* @__PURE__ */ f("div", {
134
+ className: i.header,
135
+ children: [/* @__PURE__ */ d("span", {
136
+ className: i.progress,
137
+ children: w.progress(D + 1, h.length)
138
+ }), /* @__PURE__ */ d("button", {
139
+ type: "button",
140
+ className: i.close,
141
+ "aria-label": w.close,
142
+ onClick: _,
143
+ children: "×"
144
+ })]
145
+ }),
146
+ O.title && /* @__PURE__ */ d("h2", {
147
+ className: i.title,
148
+ id: I,
149
+ children: O.title
150
+ }),
151
+ /* @__PURE__ */ d("div", {
152
+ className: i.body,
153
+ id: L,
154
+ children: O.body
155
+ }),
156
+ /* @__PURE__ */ f("div", {
157
+ className: i.footer,
158
+ children: [!B && /* @__PURE__ */ d("button", {
159
+ type: "button",
160
+ className: i.skip,
161
+ onClick: _,
162
+ children: w.skip
163
+ }), /* @__PURE__ */ f("div", {
164
+ className: i.actions,
165
+ children: [D > 0 && /* @__PURE__ */ d("button", {
166
+ type: "button",
167
+ className: i.secondary,
168
+ onClick: () => R(D - 1),
169
+ children: w.back
170
+ }), /* @__PURE__ */ d("button", {
171
+ type: "button",
172
+ className: i.primary,
173
+ onClick: () => B ? V() : R(D + 1),
174
+ children: B ? w.finish : w.next
175
+ })]
176
+ })]
177
+ })
178
+ ]
179
+ })
180
+ ]
181
+ });
182
+ }
183
+ //#endregion
184
+ export { h as Tour };
185
+
186
+ //# sourceMappingURL=Tour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tour.js","names":[],"sources":["../../../src/components/Tour/Tour.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\n\nimport { useFocusTrap } from \"@/hooks/use-focus-trap\";\nimport { cn } from \"@/utils/cn\";\n\nimport { backdropRects, placeCard, type TourPlacement, type TourRect } from \"./tour-position\";\nimport styles from \"./Tour.module.css\";\n\n/** One stop of the tour. */\nexport interface TourStep {\n /**\n * CSS selector of the element to point at.\n *\n * A selector rather than a ref, so a tour can be declared as data — in a config\n * file, from the backend, next to the copy — without every screen having to\n * thread refs up to whoever renders the tour.\n */\n target?: string;\n /** Heading of the card. */\n title?: ReactNode;\n /** The explanation. */\n body: ReactNode;\n /** Preferred side. Default `\"bottom\"`, flipped when it does not fit. */\n placement?: TourPlacement;\n}\n\n/** Labels, per locale. */\ninterface TourStrings {\n next: string;\n back: string;\n finish: string;\n skip: string;\n close: string;\n progress: (current: number, total: number) => string;\n}\n\nconst PT_BR: TourStrings = {\n next: \"Próximo\",\n back: \"Voltar\",\n finish: \"Concluir\",\n skip: \"Pular\",\n close: \"Fechar tour\",\n progress: (current, total) => `Passo ${current} de ${total}`,\n};\n\nconst EN: TourStrings = {\n next: \"Next\",\n back: \"Back\",\n finish: \"Done\",\n skip: \"Skip\",\n close: \"Close tour\",\n progress: (current, total) => `Step ${current} of ${total}`,\n};\n\nexport interface TourProps {\n /** The stops, in order. */\n steps: readonly TourStep[];\n /** Whether the tour is showing. Controlled. */\n open: boolean;\n /** Closed by `Esc`, by the close button or by \"skip\". */\n onClose: () => void;\n /** Called after the last step is confirmed. `onClose` follows it. */\n onFinish?: () => void;\n /** Current step index, when the app wants to drive it. */\n index?: number;\n /** Step changed — required only when `index` is given. */\n onIndexChange?: (index: number) => void;\n /** Locale for the labels. Default `\"pt-BR\"`. */\n locale?: \"pt-BR\" | \"en\";\n /** Space kept clear around the highlighted element, in px. Default 4. */\n spotlightPadding?: number;\n /** Extra class on the card. */\n className?: string;\n}\n\n/**\n * A guided tour: dim the page, highlight one element at a time, explain it.\n *\n * The highlighted element stays **clickable** while everything else is blocked,\n * because the useful kind of coachmark says \"press this\" and lets you press it. That\n * is why the backdrop is four rectangles around the target rather than one overlay\n * with a `box-shadow` hole — a shadow is not hit-testable, so a hole made that way\n * would not block anything.\n *\n * Whether a user has seen the tour is the app's business: this component takes\n * `open` and emits `onClose`/`onFinish`. Persisting a flag in `localStorage` is one\n * line in the app and a wrong default here.\n *\n * @example\n * const [open, setOpen] = useState(!storage.get(\"tour-v1\"));\n *\n * <Tour\n * open={open}\n * steps={[\n * { target: \"#novo-pedido\", title: \"Comece aqui\", body: \"Todo pedido nasce deste botão.\" },\n * { target: \"[data-tour='filtros']\", body: \"E filtre por período aqui.\" },\n * ]}\n * onClose={() => setOpen(false)}\n * onFinish={() => storage.set(\"tour-v1\", true)}\n * />\n */\nexport function Tour({\n steps,\n open,\n onClose,\n onFinish,\n index,\n onIndexChange,\n locale = \"pt-BR\",\n spotlightPadding = 4,\n className,\n}: TourProps) {\n const strings = locale === \"en\" ? EN : PT_BR;\n const [internal, setInternal] = useState(0);\n const current = Math.min(index ?? internal, Math.max(0, steps.length - 1));\n const step = steps[current];\n\n const card = useRef<HTMLDivElement | null>(null);\n const [target, setTarget] = useState<TourRect | null>(null);\n const [position, setPosition] = useState<{\n placement: TourPlacement;\n top: number;\n left: number;\n }>({ placement: \"center\", top: 0, left: 0 });\n const [viewport, setViewport] = useState({ width: 0, height: 0 });\n const titleId = useId();\n const bodyId = useId();\n\n useFocusTrap(card, open);\n\n const goTo = useCallback(\n (next: number) => {\n if (index === undefined) setInternal(next);\n onIndexChange?.(next);\n },\n [index, onIndexChange],\n );\n\n /** Reset to the first step whenever the tour opens again. */\n useEffect(() => {\n if (open && index === undefined) setInternal(0);\n }, [open, index]);\n\n /**\n * Find the step's element, bring it into view, and measure everything.\n *\n * The scroll happens before measuring, and the measurement is deferred to the\n * next frame: reading a rect in the same tick as `scrollIntoView` gives the\n * position the element had *before* the scroll, which lands the card over the\n * wrong part of the page.\n */\n const measure = useCallback(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n const rect = element?.getBoundingClientRect() ?? null;\n setTarget(\n rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n );\n\n const size = {\n width: card.current?.offsetWidth ?? 320,\n height: card.current?.offsetHeight ?? 160,\n };\n const view = { width: window.innerWidth, height: window.innerHeight };\n setViewport(view);\n setPosition(\n placeCard({\n target: rect\n ? { top: rect.top, left: rect.left, width: rect.width, height: rect.height }\n : null,\n card: size,\n viewport: view,\n preferred: step.placement,\n }),\n );\n }, [open, step]);\n\n useLayoutEffect(() => {\n if (!open || !step) return;\n const element = step.target ? document.querySelector<HTMLElement>(step.target) : null;\n element?.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior: \"smooth\" });\n const frame = requestAnimationFrame(measure);\n return () => cancelAnimationFrame(frame);\n }, [open, step, measure]);\n\n useEffect(() => {\n if (!open) return;\n window.addEventListener(\"resize\", measure);\n window.addEventListener(\"scroll\", measure, true);\n return () => {\n window.removeEventListener(\"resize\", measure);\n window.removeEventListener(\"scroll\", measure, true);\n };\n }, [open, measure]);\n\n useEffect(() => {\n if (open) card.current?.focus();\n }, [open, current]);\n\n if (!open || !step) return null;\n\n const last = current === steps.length - 1;\n\n const finish = (): void => {\n onFinish?.();\n onClose();\n };\n\n /**\n * Arrow keys walk the tour and `Esc` leaves it.\n *\n * `Esc` is handled here rather than on `window` so a tour opened over a modal\n * does not close both: the innermost handler that sees the key wins, and the\n * card has focus.\n */\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === \"Escape\") {\n event.stopPropagation();\n onClose();\n return;\n }\n if (event.key === \"ArrowRight\" && !last) {\n event.preventDefault();\n goTo(current + 1);\n }\n if (event.key === \"ArrowLeft\" && current > 0) {\n event.preventDefault();\n goTo(current - 1);\n }\n };\n\n return (\n <div className={styles.root} data-placement={position.placement}>\n {backdropRects(target, viewport, spotlightPadding).map((rect, i) => (\n <div\n key={i}\n className={styles.backdrop}\n style={{\n top: rect.top,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n }}\n onClick={onClose}\n />\n ))}\n\n {target && (\n <div\n className={styles.spotlight}\n style={{\n top: target.top - spotlightPadding,\n left: target.left - spotlightPadding,\n width: target.width + spotlightPadding * 2,\n height: target.height + spotlightPadding * 2,\n }}\n />\n )}\n\n <div\n ref={card}\n className={cn(styles.card, className)}\n style={{ top: position.top, left: position.left }}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={step.title ? titleId : undefined}\n aria-describedby={bodyId}\n tabIndex={-1}\n onKeyDown={handleKeyDown}\n >\n <div className={styles.header}>\n <span className={styles.progress}>\n {strings.progress(current + 1, steps.length)}\n </span>\n <button\n type=\"button\"\n className={styles.close}\n aria-label={strings.close}\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {step.title && (\n <h2 className={styles.title} id={titleId}>\n {step.title}\n </h2>\n )}\n <div className={styles.body} id={bodyId}>\n {step.body}\n </div>\n\n <div className={styles.footer}>\n {!last && (\n <button type=\"button\" className={styles.skip} onClick={onClose}>\n {strings.skip}\n </button>\n )}\n <div className={styles.actions}>\n {current > 0 && (\n <button\n type=\"button\"\n className={styles.secondary}\n onClick={() => goTo(current - 1)}\n >\n {strings.back}\n </button>\n )}\n <button\n type=\"button\"\n className={styles.primary}\n onClick={() => (last ? finish() : goTo(current + 1))}\n >\n {last ? strings.finish : strings.next}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AA6CA,IAAM,IAAqB;CACvB,MAAM;CACN,MAAM;CACN,QAAQ;CACR,MAAM;CACN,OAAO;CACP,WAAW,GAAS,MAAU,SAAS,EAAQ,MAAM;AACzD,GAEM,IAAkB;CACpB,MAAM;CACN,MAAM;CACN,QAAQ;CACR,MAAM;CACN,OAAO;CACP,WAAW,GAAS,MAAU,QAAQ,EAAQ,MAAM;AACxD;AAiDA,SAAgB,EAAK,EACjB,UACA,SACA,YACA,aACA,UACA,kBACA,YAAS,SACT,sBAAmB,GACnB,gBACU;CACV,IAAM,IAAU,MAAW,OAAO,IAAK,GACjC,CAAC,GAAU,KAAe,EAAS,CAAC,GACpC,IAAU,KAAK,IAAI,KAAS,GAAU,KAAK,IAAI,GAAG,EAAM,SAAS,CAAC,CAAC,GACnE,IAAO,EAAM,IAEb,IAAO,EAA8B,IAAI,GACzC,CAAC,GAAQ,KAAa,EAA0B,IAAI,GACpD,CAAC,GAAU,KAAe,EAI7B;EAAE,WAAW;EAAU,KAAK;EAAG,MAAM;CAAE,CAAC,GACrC,CAAC,GAAU,KAAe,EAAS;EAAE,OAAO;EAAG,QAAQ;CAAE,CAAC,GAC1D,IAAU,EAAM,GAChB,IAAS,EAAM;CAErB,EAAa,GAAM,CAAI;CAEvB,IAAM,IAAO,GACR,MAAiB;EAEd,AADI,MAAU,KAAA,KAAW,EAAY,CAAI,GACzC,IAAgB,CAAI;CACxB,GACA,CAAC,GAAO,CAAa,CACzB;CAGA,QAAgB;EACZ,AAAI,KAAQ,MAAU,KAAA,KAAW,EAAY,CAAC;CAClD,GAAG,CAAC,GAAM,CAAK,CAAC;CAUhB,IAAM,IAAU,QAAkB;EAC9B,IAAI,CAAC,KAAQ,CAAC,GAAM;EAEpB,IAAM,KADU,EAAK,SAAS,SAAS,cAA2B,EAAK,MAAM,IAAI,KAAA,EAC3D,sBAAsB,KAAK;EACjD,EACI,IACM;GAAE,KAAK,EAAK;GAAK,MAAM,EAAK;GAAM,OAAO,EAAK;GAAO,QAAQ,EAAK;EAAO,IACzE,IACV;EAEA,IAAM,IAAO;GACT,OAAO,EAAK,SAAS,eAAe;GACpC,QAAQ,EAAK,SAAS,gBAAgB;EAC1C,GACM,IAAO;GAAE,OAAO,OAAO;GAAY,QAAQ,OAAO;EAAY;EAEpE,AADA,EAAY,CAAI,GAChB,EACI,EAAU;GACN,QAAQ,IACF;IAAE,KAAK,EAAK;IAAK,MAAM,EAAK;IAAM,OAAO,EAAK;IAAO,QAAQ,EAAK;GAAO,IACzE;GACN,MAAM;GACN,UAAU;GACV,WAAW,EAAK;EACpB,CAAC,CACL;CACJ,GAAG,CAAC,GAAM,CAAI,CAAC;CAwBf,IAtBA,QAAsB;EAClB,IAAI,CAAC,KAAQ,CAAC,GAAM;EAEpB,CADgB,EAAK,SAAS,SAAS,cAA2B,EAAK,MAAM,IAAI,KAAA,EACxE,eAAe;GAAE,OAAO;GAAU,QAAQ;GAAW,UAAU;EAAS,CAAC;EAClF,IAAM,IAAQ,sBAAsB,CAAO;EAC3C,aAAa,qBAAqB,CAAK;CAC3C,GAAG;EAAC;EAAM;EAAM;CAAO,CAAC,GAExB,QAAgB;EACP,OAGL,OAFA,OAAO,iBAAiB,UAAU,CAAO,GACzC,OAAO,iBAAiB,UAAU,GAAS,EAAI,SAClC;GAET,AADA,OAAO,oBAAoB,UAAU,CAAO,GAC5C,OAAO,oBAAoB,UAAU,GAAS,EAAI;EACtD;CACJ,GAAG,CAAC,GAAM,CAAO,CAAC,GAElB,QAAgB;EACZ,AAAI,KAAM,EAAK,SAAS,MAAM;CAClC,GAAG,CAAC,GAAM,CAAO,CAAC,GAEd,CAAC,KAAQ,CAAC,GAAM,OAAO;CAE3B,IAAM,IAAO,MAAY,EAAM,SAAS,GAElC,UAAqB;EAEvB,AADA,IAAW,GACX,EAAQ;CACZ;CAyBA,OACI,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAM,kBAAgB,EAAS;YAAtD;GACK,EAAc,GAAQ,GAAU,CAAgB,CAAC,CAAC,KAAK,GAAM,MAC1D,kBAAC,OAAD;IAEI,WAAW,EAAO;IAClB,OAAO;KACH,KAAK,EAAK;KACV,MAAM,EAAK;KACX,OAAO,EAAK;KACZ,QAAQ,EAAK;IACjB;IACA,SAAS;GACZ,GATQ,CASR,CACJ;GAEA,KACG,kBAAC,OAAD;IACI,WAAW,EAAO;IAClB,OAAO;KACH,KAAK,EAAO,MAAM;KAClB,MAAM,EAAO,OAAO;KACpB,OAAO,EAAO,QAAQ,IAAmB;KACzC,QAAQ,EAAO,SAAS,IAAmB;IAC/C;GACH,CAAA;GAGL,kBAAC,OAAD;IACI,KAAK;IACL,WAAW,EAAG,EAAO,MAAM,CAAS;IACpC,OAAO;KAAE,KAAK,EAAS;KAAK,MAAM,EAAS;IAAK;IAChD,MAAK;IACL,cAAW;IACX,mBAAiB,EAAK,QAAQ,IAAU,KAAA;IACxC,oBAAkB;IAClB,UAAU;IACV,YArDW,MAA+C;KAClE,IAAI,EAAM,QAAQ,UAAU;MAExB,AADA,EAAM,gBAAgB,GACtB,EAAQ;MACR;KACJ;KAKA,AAJI,EAAM,QAAQ,gBAAgB,CAAC,MAC/B,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC,IAEhB,EAAM,QAAQ,eAAe,IAAU,MACvC,EAAM,eAAe,GACrB,EAAK,IAAU,CAAC;IAExB;cA8BQ;KAWI,kBAAC,OAAD;MAAK,WAAW,EAAO;gBAAvB,CACI,kBAAC,QAAD;OAAM,WAAW,EAAO;iBACnB,EAAQ,SAAS,IAAU,GAAG,EAAM,MAAM;MACzC,CAAA,GACN,kBAAC,UAAD;OACI,MAAK;OACL,WAAW,EAAO;OAClB,cAAY,EAAQ;OACpB,SAAS;iBACZ;MAEO,CAAA,CACP;;KAEJ,EAAK,SACF,kBAAC,MAAD;MAAI,WAAW,EAAO;MAAO,IAAI;gBAC5B,EAAK;KACN,CAAA;KAER,kBAAC,OAAD;MAAK,WAAW,EAAO;MAAM,IAAI;gBAC5B,EAAK;KACL,CAAA;KAEL,kBAAC,OAAD;MAAK,WAAW,EAAO;gBAAvB,CACK,CAAC,KACE,kBAAC,UAAD;OAAQ,MAAK;OAAS,WAAW,EAAO;OAAM,SAAS;iBAClD,EAAQ;MACL,CAAA,GAEZ,kBAAC,OAAD;OAAK,WAAW,EAAO;iBAAvB,CACK,IAAU,KACP,kBAAC,UAAD;QACI,MAAK;QACL,WAAW,EAAO;QAClB,eAAe,EAAK,IAAU,CAAC;kBAE9B,EAAQ;OACL,CAAA,GAEZ,kBAAC,UAAD;QACI,MAAK;QACL,WAAW,EAAO;QAClB,eAAgB,IAAO,EAAO,IAAI,EAAK,IAAU,CAAC;kBAEjD,IAAO,EAAQ,SAAS,EAAQ;OAC7B,CAAA,CACP;QACJ;;IACJ;;EACJ;;AAEb"}
@@ -0,0 +1,2 @@
1
+ var e=`tempest_root_o2Dqb`,t=`tempest_backdrop_TVqRI`,n=`tempest_spotlight_0N62-`,r=`tempest_card_eurpw`,i=`tempest_header_PTKGS`,a=`tempest_progress_WzQ2f`,o=`tempest_close_z43ed`,s=`tempest_title_an3mi`,c=`tempest_body_RUlwz`,l=`tempest_footer_i-Mo6`,u=`tempest_actions_n-L-Q`,d=`tempest_skip_IdWQY`,f=`tempest_primary_ICXJm`,p=`tempest_secondary_kde2L`,m={root:e,backdrop:t,spotlight:n,card:r,header:i,progress:a,close:o,title:s,body:c,footer:l,actions:u,skip:d,primary:f,secondary:p};exports.actions=u,exports.backdrop=t,exports.body=c,exports.card=r,exports.close=o,exports.default=m,exports.footer=l,exports.header=i,exports.primary=f,exports.progress=a,exports.root=e,exports.secondary=p,exports.skip=d,exports.spotlight=n,exports.title=s;
2
+ //# sourceMappingURL=Tour.module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tour.module.cjs","names":[],"sources":["../../../src/components/Tour/Tour.module.css"],"sourcesContent":[".root {\n position: fixed;\n inset: 0;\n z-index: var(--tempest-z-modal, 1000);\n font-family: var(--tempest-font-sans);\n pointer-events: none;\n}\n\n/*\n * The dimmed area is four rectangles, each one hit-testable, so the page behind is\n * blocked while the highlighted element stays clickable. A single overlay with a\n * `box-shadow` hole looks the same and blocks nothing: a shadow is not hit-tested.\n */\n.backdrop {\n position: fixed;\n background-color: var(--tempest-tour-backdrop, rgb(0 0 0 / 55%));\n pointer-events: auto;\n}\n\n.spotlight {\n position: fixed;\n border-radius: var(--tempest-radius-md);\n box-shadow: 0 0 0 2px var(--tempest-primary);\n pointer-events: none;\n}\n\n.card {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n width: min(22rem, calc(100vw - 2rem));\n padding: var(--tempest-space-4);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n box-shadow: var(--tempest-shadow-lg);\n color: var(--tempest-text);\n pointer-events: auto;\n}\n\n.card:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n}\n\n.progress {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: none;\n border-radius: var(--tempest-radius-sm);\n background: none;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-lg);\n line-height: 1;\n cursor: pointer;\n}\n\n.close:hover {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.body {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n margin-block-start: var(--tempest-space-2);\n}\n\n.actions {\n display: flex;\n gap: var(--tempest-space-2);\n margin-inline-start: auto;\n}\n\n.skip {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-text-muted);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.primary,\n.secondary {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border-radius: var(--tempest-radius-md);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.primary {\n border: none;\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.secondary {\n border: 1px solid var(--tempest-border);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.primary:focus-visible,\n.secondary:focus-visible,\n.skip:focus-visible,\n.close:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n/* The spotlight ring is decoration; a reader who asked for less motion gets none. */\n@media (prefers-reduced-motion: no-preference) {\n .spotlight {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out),\n width var(--tempest-duration-base) var(--tempest-ease-out),\n height var(--tempest-duration-base) var(--tempest-ease-out);\n }\n\n .card {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out);\n }\n}\n"],"mappings":""}
@@ -0,0 +1,21 @@
1
+ //#region src/components/Tour/Tour.module.css
2
+ var e = "tempest_root_o2Dqb", t = "tempest_backdrop_TVqRI", n = "tempest_spotlight_0N62-", r = "tempest_card_eurpw", i = "tempest_header_PTKGS", a = "tempest_progress_WzQ2f", o = "tempest_close_z43ed", s = "tempest_title_an3mi", c = "tempest_body_RUlwz", l = "tempest_footer_i-Mo6", u = "tempest_actions_n-L-Q", d = "tempest_skip_IdWQY", f = "tempest_primary_ICXJm", p = "tempest_secondary_kde2L", m = {
3
+ root: e,
4
+ backdrop: t,
5
+ spotlight: n,
6
+ card: r,
7
+ header: i,
8
+ progress: a,
9
+ close: o,
10
+ title: s,
11
+ body: c,
12
+ footer: l,
13
+ actions: u,
14
+ skip: d,
15
+ primary: f,
16
+ secondary: p
17
+ };
18
+ //#endregion
19
+ export { u as actions, t as backdrop, c as body, r as card, o as close, m as default, l as footer, i as header, f as primary, a as progress, e as root, p as secondary, d as skip, n as spotlight, s as title };
20
+
21
+ //# sourceMappingURL=Tour.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tour.module.js","names":[],"sources":["../../../src/components/Tour/Tour.module.css"],"sourcesContent":[".root {\n position: fixed;\n inset: 0;\n z-index: var(--tempest-z-modal, 1000);\n font-family: var(--tempest-font-sans);\n pointer-events: none;\n}\n\n/*\n * The dimmed area is four rectangles, each one hit-testable, so the page behind is\n * blocked while the highlighted element stays clickable. A single overlay with a\n * `box-shadow` hole looks the same and blocks nothing: a shadow is not hit-tested.\n */\n.backdrop {\n position: fixed;\n background-color: var(--tempest-tour-backdrop, rgb(0 0 0 / 55%));\n pointer-events: auto;\n}\n\n.spotlight {\n position: fixed;\n border-radius: var(--tempest-radius-md);\n box-shadow: 0 0 0 2px var(--tempest-primary);\n pointer-events: none;\n}\n\n.card {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: var(--tempest-space-2);\n width: min(22rem, calc(100vw - 2rem));\n padding: var(--tempest-space-4);\n border-radius: var(--tempest-radius-lg);\n background-color: var(--tempest-bg);\n box-shadow: var(--tempest-shadow-lg);\n color: var(--tempest-text);\n pointer-events: auto;\n}\n\n.card:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-2);\n}\n\n.progress {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-xs);\n font-variant-numeric: tabular-nums;\n}\n\n.close {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 1.75rem;\n height: 1.75rem;\n padding: 0;\n border: none;\n border-radius: var(--tempest-radius-sm);\n background: none;\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-lg);\n line-height: 1;\n cursor: pointer;\n}\n\n.close:hover {\n background-color: var(--tempest-surface);\n color: var(--tempest-text);\n}\n\n.title {\n margin: 0;\n font-size: var(--tempest-text-base);\n font-weight: var(--tempest-weight-semibold);\n}\n\n.body {\n color: var(--tempest-text-muted);\n font-size: var(--tempest-text-sm);\n line-height: var(--tempest-leading-normal);\n}\n\n.footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--tempest-space-3);\n margin-block-start: var(--tempest-space-2);\n}\n\n.actions {\n display: flex;\n gap: var(--tempest-space-2);\n margin-inline-start: auto;\n}\n\n.skip {\n padding: 0;\n border: none;\n background: none;\n color: var(--tempest-text-muted);\n font: inherit;\n font-size: var(--tempest-text-xs);\n text-decoration: underline;\n cursor: pointer;\n}\n\n.primary,\n.secondary {\n padding: var(--tempest-space-2) var(--tempest-space-4);\n border-radius: var(--tempest-radius-md);\n font: inherit;\n font-size: var(--tempest-text-sm);\n font-weight: var(--tempest-weight-medium);\n cursor: pointer;\n}\n\n.primary {\n border: none;\n background-color: var(--tempest-primary);\n color: var(--tempest-primary-foreground);\n}\n\n.secondary {\n border: 1px solid var(--tempest-border);\n background-color: var(--tempest-bg);\n color: var(--tempest-text);\n}\n\n.primary:focus-visible,\n.secondary:focus-visible,\n.skip:focus-visible,\n.close:focus-visible {\n outline: 2px solid var(--tempest-primary);\n outline-offset: 2px;\n}\n\n/* The spotlight ring is decoration; a reader who asked for less motion gets none. */\n@media (prefers-reduced-motion: no-preference) {\n .spotlight {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out),\n width var(--tempest-duration-base) var(--tempest-ease-out),\n height var(--tempest-duration-base) var(--tempest-ease-out);\n }\n\n .card {\n transition:\n top var(--tempest-duration-base) var(--tempest-ease-out),\n left var(--tempest-duration-base) var(--tempest-ease-out);\n }\n}\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n){return Math.max(t,Math.min(e,Math.max(t,n)))}function t(e,t,n,r){switch(e){case`top`:return t.top-12-n.height>=8;case`bottom`:return t.top+t.height+12+n.height<=r.height-8;case`left`:return t.left-12-n.width>=8;case`right`:return t.left+t.width+12+n.width<=r.width-8;default:return!0}}var n={bottom:[`bottom`,`top`,`right`,`left`],top:[`top`,`bottom`,`right`,`left`],right:[`right`,`left`,`bottom`,`top`],left:[`left`,`right`,`bottom`,`top`]};function r({target:r,card:i,viewport:a,preferred:o=`bottom`}){let s={placement:`center`,top:Math.max(8,(a.height-i.height)/2),left:Math.max(8,(a.width-i.width)/2)};if(!r||o===`center`)return s;let c=(n[o]??n.bottom).find(e=>t(e,r,i,a));if(!c)return s;let l=r.left+r.width/2-i.width/2,u=r.top+r.height/2-i.height/2,d=c===`top`?{top:r.top-12-i.height,left:l}:c===`bottom`?{top:r.top+r.height+12,left:l}:c===`left`?{top:u,left:r.left-12-i.width}:{top:u,left:r.left+r.width+12};return{placement:c,top:e(d.top,8,a.height-i.height-8),left:e(d.left,8,a.width-i.width-8)}}function i(e,t,n=4){if(!e)return[{top:0,left:0,width:t.width,height:t.height}];let r=Math.max(0,e.top-n),i=Math.max(0,e.left-n),a=Math.min(t.width,e.left+e.width+n),o=Math.min(t.height,e.top+e.height+n);return[{top:0,left:0,width:t.width,height:r},{top:o,left:0,width:t.width,height:t.height-o},{top:r,left:0,width:i,height:o-r},{top:r,left:a,width:t.width-a,height:o-r}].filter(e=>e.width>0&&e.height>0)}exports.backdropRects=i,exports.placeCard=r;
2
+ //# sourceMappingURL=tour-position.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tour-position.cjs","names":[],"sources":["../../../src/components/Tour/tour-position.ts"],"sourcesContent":["/** Where the card sits relative to the highlighted element. */\nexport type TourPlacement = \"top\" | \"bottom\" | \"left\" | \"right\" | \"center\";\n\n/** A rectangle in viewport coordinates. */\nexport interface TourRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/** Viewport size the placement is solved against. */\nexport interface TourViewport {\n width: number;\n height: number;\n}\n\n/** Gap between the highlighted element and the card. */\nconst OFFSET = 12;\n\n/** Smallest margin the card keeps from the viewport edge. */\nconst MARGIN = 8;\n\n/** Clamp a value into a range, tolerating a range narrower than the value. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, Math.max(min, max)));\n}\n\n/** Does the card fit on this side of the target? */\nfunction fits(\n placement: TourPlacement,\n target: TourRect,\n card: { width: number; height: number },\n viewport: TourViewport,\n): boolean {\n switch (placement) {\n case \"top\":\n return target.top - OFFSET - card.height >= MARGIN;\n case \"bottom\":\n return target.top + target.height + OFFSET + card.height <= viewport.height - MARGIN;\n case \"left\":\n return target.left - OFFSET - card.width >= MARGIN;\n case \"right\":\n return target.left + target.width + OFFSET + card.width <= viewport.width - MARGIN;\n default:\n return true;\n }\n}\n\n/** Sides tried, in order, when the preferred one does not fit. */\nconst FALLBACKS: Record<Exclude<TourPlacement, \"center\">, TourPlacement[]> = {\n bottom: [\"bottom\", \"top\", \"right\", \"left\"],\n top: [\"top\", \"bottom\", \"right\", \"left\"],\n right: [\"right\", \"left\", \"bottom\", \"top\"],\n left: [\"left\", \"right\", \"bottom\", \"top\"],\n};\n\n/**\n * Place the tour card next to the highlighted element.\n *\n * The preferred side is tried first and the opposite one second, because flipping\n * a \"below\" card to \"above\" keeps the reading relationship with the target;\n * jumping to a side would move the card across the screen for no reason the reader\n * can see.\n *\n * Nothing fitting anywhere ends at `center`: a card pinned half off-screen is worse\n * than a card in the middle, and this happens for real whenever the target is\n * taller than the viewport.\n *\n * The result is always clamped inside the viewport, so the card cannot be pushed\n * off the edge by a target near a corner.\n *\n * @param params.target - The highlighted element's rect, viewport coordinates.\n * @param params.card - Measured card size.\n * @param params.viewport - Viewport size.\n * @param params.preferred - Placement the step asked for. Default `\"bottom\"`.\n * @returns The chosen placement and the card's top/left.\n */\nexport function placeCard({\n target,\n card,\n viewport,\n preferred = \"bottom\",\n}: {\n target: TourRect | null;\n card: { width: number; height: number };\n viewport: TourViewport;\n preferred?: TourPlacement;\n}): { placement: TourPlacement; top: number; left: number } {\n const centered = {\n placement: \"center\" as TourPlacement,\n top: Math.max(MARGIN, (viewport.height - card.height) / 2),\n left: Math.max(MARGIN, (viewport.width - card.width) / 2),\n };\n\n // No target — a step whose element is not on the page still has to show its\n // message, so it is shown centered rather than dropped.\n if (!target || preferred === \"center\") return centered;\n\n const order = FALLBACKS[preferred as Exclude<TourPlacement, \"center\">] ?? FALLBACKS.bottom;\n const placement = order.find((candidate) => fits(candidate, target, card, viewport));\n if (!placement) return centered;\n\n const horizontalCenter = target.left + target.width / 2 - card.width / 2;\n const verticalCenter = target.top + target.height / 2 - card.height / 2;\n\n const raw =\n placement === \"top\"\n ? { top: target.top - OFFSET - card.height, left: horizontalCenter }\n : placement === \"bottom\"\n ? { top: target.top + target.height + OFFSET, left: horizontalCenter }\n : placement === \"left\"\n ? { top: verticalCenter, left: target.left - OFFSET - card.width }\n : { top: verticalCenter, left: target.left + target.width + OFFSET };\n\n return {\n placement,\n top: clamp(raw.top, MARGIN, viewport.height - card.height - MARGIN),\n left: clamp(raw.left, MARGIN, viewport.width - card.width - MARGIN),\n };\n}\n\n/**\n * The four backdrop rectangles that surround the highlighted element.\n *\n * Four rects rather than one overlay with a `box-shadow` hole: a box-shadow is not\n * hit-testable, so the \"dimmed\" area would not block clicks — and blocking the rest\n * of the page while leaving the highlighted control usable is the whole behaviour a\n * coachmark needs. Any rect that comes out empty is dropped, which is what happens\n * when the target touches an edge.\n *\n * @param target - Highlighted rect, or `null` for a full-screen backdrop.\n * @param viewport - Viewport size.\n * @param padding - Extra space kept clear around the target.\n */\nexport function backdropRects(\n target: TourRect | null,\n viewport: TourViewport,\n padding = 4,\n): TourRect[] {\n if (!target) {\n return [{ top: 0, left: 0, width: viewport.width, height: viewport.height }];\n }\n const top = Math.max(0, target.top - padding);\n const left = Math.max(0, target.left - padding);\n const right = Math.min(viewport.width, target.left + target.width + padding);\n const bottom = Math.min(viewport.height, target.top + target.height + padding);\n\n return [\n { top: 0, left: 0, width: viewport.width, height: top },\n { top: bottom, left: 0, width: viewport.width, height: viewport.height - bottom },\n { top, left: 0, width: left, height: bottom - top },\n { top, left: right, width: viewport.width - right, height: bottom - top },\n ].filter((rect) => rect.width > 0 && rect.height > 0);\n}\n"],"mappings":"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"}