jaci-ui 0.9.1 → 0.9.3

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.3
4
+
5
+ ### Patch Changes
6
+
7
+ - caa17c1: Allow Button links composed with `render={<a />}` to render safely from Next.js server components.
8
+ - 3efbbee: Keep Accordion roots at a stable responsive width when all panels are collapsed.
9
+
10
+ ## 0.9.2
11
+
12
+ ### Patch Changes
13
+
14
+ - 894b4e8: Update Storybook and compatible workspace tooling dependencies to their latest patch releases.
15
+ - 10446a3: Fix `Button.render` with native and framework link elements, including Next.js client boundaries.
16
+ - cdcfb85: Improve QRCode finder contrast in dark themes and add native SVG download support.
17
+ - 77a1b9f: Improve QRCode's native SVG download link accessibility and preserve the QR code's image semantics.
18
+
3
19
  ## 0.9.1
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -38,6 +38,9 @@ export function Example() {
38
38
  <Stack gap="md">
39
39
  <Button variant="solid">Continue</Button>
40
40
 
41
+ {/* Button links also work in a Next App Router Server Component. */}
42
+ <Button variant="outline" render={<a href="/docs" />}>Read the docs</Button>
43
+
41
44
  <ColorPicker.Root defaultValue="#2563eb" name="brand-color">
42
45
  <ColorPicker.Label>Brand color</ColorPicker.Label>
43
46
  <ColorPicker.Control>
@@ -84,10 +87,10 @@ import { AspectRatio, Code, Figure, Image, Kbd, Quote, Stat, StatGroup } from "j
84
87
  </StatGroup.Root>;
85
88
  ```
86
89
 
87
- ### Mídia e utilitários visuais
90
+ ### Media and visual utilities
88
91
 
89
- `Carousel` oferece slides acessíveis com navegação por teclado, indicadores, swipe e autoplay
90
- opcional. Sempre forneça um `aria-label` no root e texto alternativo nas imagens:
92
+ `Carousel` provides accessible slides with keyboard navigation, indicators, swipe support and
93
+ optional autoplay. Always provide an `aria-label` on the root and alternative text for images:
91
94
 
92
95
  ```tsx
93
96
  <Carousel.Root aria-label="Featured projects" loop>
@@ -108,8 +111,8 @@ opcional. Sempre forneça um `aria-label` no root e texto alternativo nas imagen
108
111
  </Carousel.Root>
109
112
  ```
110
113
 
111
- `QRCode` gera SVG no servidor e no cliente, com módulos circulares e olhos quadrados
112
- arredondados. O valor pode ser controlado diretamente pela aplicação:
114
+ `QRCode` generates SVG on the server and client, with circular modules and rounded square eyes.
115
+ The application can control the value directly:
113
116
 
114
117
  ```tsx
115
118
  <QRCode
@@ -117,10 +120,15 @@ arredondados. O valor pode ser controlado diretamente pela aplicação:
117
120
  size={240}
118
121
  errorCorrectionLevel="Q"
119
122
  label="Jaci UI website"
123
+ download="jaci-ui-qr.svg"
124
+ downloadLabel="Download Jaci UI QR code"
120
125
  />
121
126
  ```
122
127
 
123
- Para downloads, `DownloadTrigger` é um link HTML comum e não faz fetch nem cria `Blob`:
128
+ When `download` is provided, the SVG exposes a native link for downloading the code without
129
+ using browser APIs during SSR.
130
+
131
+ For downloads, `DownloadTrigger` is a regular HTML link: it does not fetch or create a `Blob`:
124
132
 
125
133
  ```tsx
126
134
  <DownloadTrigger href="/files/report.pdf" download="report.pdf">
@@ -128,19 +136,19 @@ Para downloads, `DownloadTrigger` é um link HTML comum e não faz fetch nem cri
128
136
  </DownloadTrigger>
129
137
  ```
130
138
 
131
- Em URLs cross-origin, o nome e a própria autorização do download continuam sujeitos às regras
132
- do navegador e aos headers do servidor.
139
+ For cross-origin URLs, the filename and the download authorization remain subject to browser
140
+ rules and server headers.
133
141
 
134
- Use `Separator` para uma linha semântica ou decorativa entre seções e `Spacer` quando precisar
135
- apenas de espaço, sem linha:
142
+ Use `Separator` for a semantic or decorative line between sections, and `Spacer` when only
143
+ space—not a line—is needed:
136
144
 
137
145
  ```tsx
138
146
  <Separator orientation="horizontal" />
139
147
  <Spacer axis="vertical" size="lg" />
140
148
  ```
141
149
 
142
- Todos esses componentes continuam compatíveis com SSR e React 18/19. `Carousel` é o único novo
143
- módulo interativo; os demais não acessam APIs do navegador durante importação ou renderização.
150
+ These components support SSR and React 18/19. Interactive modules declare their client boundary
151
+ where required and do not access browser APIs during import or server rendering.
144
152
 
145
153
  `AspectRatio` fills the available inline size of its parent. In shrink-to-fit or flex
146
154
  layouts, give the parent an explicit `width`/`max-width` so the ratio has a measurable
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  const require_cx = require("../../styled-system/css/cx.cjs");
3
2
  const require_button = require("../../styled-system/recipes/button.cjs");
4
3
  const require_index = require("../layout/index.cjs");
@@ -16,7 +15,7 @@ function content(children, loading, startIcon, endIcon) {
16
15
  !loading ? endIcon : void 0
17
16
  ] });
18
17
  }
19
- const Button = (0, react.forwardRef)(function Button({ children, className, variant = "outline", size = "md", fullWidth = false, loading = false, startIcon, endIcon, disabled, render, type = "button", ...props }, ref) {
18
+ const Button = (0, react.forwardRef)(function Button({ children, className, variant = "outline", size = "md", fullWidth = false, loading = false, startIcon, endIcon, disabled, onClick: buttonOnClick, render, type = "button", ...props }, ref) {
20
19
  const buttonClassName = require_cx.cx(require_button.button({
21
20
  variant,
22
21
  size,
@@ -24,7 +23,13 @@ const Button = (0, react.forwardRef)(function Button({ children, className, vari
24
23
  }), className);
25
24
  const childrenWithIcons = content(children, loading, startIcon, endIcon);
26
25
  if (render) {
27
- const renderedProps = render.props;
26
+ const renderedProps = render.props ?? {};
27
+ const renderedOnClick = renderedProps.onClick;
28
+ const handleRenderedClick = renderedOnClick || buttonOnClick ? disabled || loading ? (event) => event.preventDefault() : (event) => {
29
+ renderedOnClick?.(event);
30
+ if (!event.defaultPrevented) buttonOnClick?.(event);
31
+ } : void 0;
32
+ const renderedHref = disabled || loading ? render.type === "a" ? void 0 : renderedProps.href : renderedProps.href;
28
33
  return (0, react.cloneElement)(render, {
29
34
  ...props,
30
35
  "aria-busy": loading || void 0,
@@ -33,7 +38,11 @@ const Button = (0, react.forwardRef)(function Button({ children, className, vari
33
38
  "data-jaci-component": "button",
34
39
  "data-slot": "button",
35
40
  children: childrenWithIcons,
36
- onClick: disabled || loading ? void 0 : renderedProps.onClick
41
+ onClick: handleRenderedClick,
42
+ ...render.type === "a" && (disabled || loading) ? {
43
+ href: renderedHref,
44
+ tabIndex: -1
45
+ } : {}
37
46
  });
38
47
  }
39
48
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -43,6 +52,7 @@ const Button = (0, react.forwardRef)(function Button({ children, className, vari
43
52
  "data-jaci-component": "button",
44
53
  "data-slot": "button",
45
54
  disabled: disabled || loading,
55
+ onClick: buttonOnClick,
46
56
  ref,
47
57
  type,
48
58
  children: childrenWithIcons
@@ -1 +1 @@
1
- {"version":3,"file":"button.cjs","names":["Spinner","cx","button"],"sources":["../../../src/components/button/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cloneElement, forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, MouseEventHandler, ReactElement, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { button } from \"../../styled-system/recipes\";\nimport { Spinner } from \"../layout\";\n\ntype RenderableButtonProps = Record<string, unknown> & {\n children?: ReactNode | undefined;\n className?: string | undefined;\n onClick?: MouseEventHandler<HTMLElement> | undefined;\n};\n\nexport interface ButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"color\" | \"children\"> {\n children?: ReactNode;\n variant?: \"outline\" | \"solid\" | \"ghost\" | \"danger\";\n size?: \"sm\" | \"md\" | \"lg\";\n fullWidth?: boolean;\n loading?: boolean;\n startIcon?: ReactNode;\n endIcon?: ReactNode;\n render?: ReactElement<RenderableButtonProps>;\n}\n\nfunction content(children: ReactNode, loading: boolean, startIcon: ReactNode, endIcon: ReactNode) {\n return (\n <>\n {loading ? <Spinner aria-hidden=\"true\" label=\"\" size=\"sm\" /> : startIcon}\n {children}\n {!loading ? endIcon : undefined}\n </>\n );\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n children,\n className,\n variant = \"outline\",\n size = \"md\",\n fullWidth = false,\n loading = false,\n startIcon,\n endIcon,\n disabled,\n render,\n type = \"button\",\n ...props\n },\n ref,\n) {\n const buttonClassName = cx(button({ variant, size, fullWidth }), className);\n const childrenWithIcons = content(children, loading, startIcon, endIcon);\n\n if (render) {\n const renderedProps = render.props as {\n className?: string;\n children?: ReactNode;\n onClick?: MouseEventHandler<HTMLElement>;\n };\n\n return cloneElement(render, {\n ...props,\n \"aria-busy\": loading || undefined,\n \"aria-disabled\": disabled || loading || undefined,\n className: cx(buttonClassName, renderedProps.className),\n \"data-jaci-component\": \"button\",\n \"data-slot\": \"button\",\n children: childrenWithIcons,\n onClick: disabled || loading ? undefined : renderedProps.onClick,\n });\n }\n\n return (\n <button\n {...props}\n aria-busy={loading || undefined}\n className={buttonClassName}\n data-jaci-component=\"button\"\n data-slot=\"button\"\n disabled={disabled || loading}\n ref={ref}\n type={type}\n >\n {childrenWithIcons}\n </button>\n );\n});\n"],"mappings":";;;;;;;AA2BA,SAAS,QAAQ,UAAqB,SAAkB,WAAsB,SAAoB;CAChG,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,UAAU,iBAAA,GAAA,kBAAA,IAAA,CAACA,cAAAA,SAAD;GAAS,eAAY;GAAO,OAAM;GAAG,MAAK;EAAM,CAAA,IAAI;EAC9D;EACA,CAAC,UAAU,UAAU,KAAA;CACtB,EAAA,CAAA;AAEN;AAEA,MAAa,UAAA,GAAA,MAAA,WAAA,CAAoD,SAAS,OACxE,EACE,UACA,WACA,UAAU,WACV,OAAO,MACP,YAAY,OACZ,UAAU,OACV,WACA,SACA,UACA,QACA,OAAO,UACP,GAAG,SAEL,KACA;CACA,MAAM,kBAAkBC,WAAAA,GAAGC,eAAAA,OAAO;EAAE;EAAS;EAAM;CAAU,CAAC,GAAG,SAAS;CAC1E,MAAM,oBAAoB,QAAQ,UAAU,SAAS,WAAW,OAAO;CAEvE,IAAI,QAAQ;EACV,MAAM,gBAAgB,OAAO;EAM7B,QAAA,GAAA,MAAA,aAAA,CAAoB,QAAQ;GAC1B,GAAG;GACH,aAAa,WAAW,KAAA;GACxB,iBAAiB,YAAY,WAAW,KAAA;GACxC,WAAWD,WAAAA,GAAG,iBAAiB,cAAc,SAAS;GACtD,uBAAuB;GACvB,aAAa;GACb,UAAU;GACV,SAAS,YAAY,UAAU,KAAA,IAAY,cAAc;EAC3D,CAAC;CACH;CAEA,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,GAAI;EACJ,aAAW,WAAW,KAAA;EACtB,WAAW;EACX,uBAAoB;EACpB,aAAU;EACV,UAAU,YAAY;EACjB;EACC;YAEL;CACK,CAAA;AAEZ,CAAC"}
1
+ {"version":3,"file":"button.cjs","names":["Spinner","cx","button"],"sources":["../../../src/components/button/button.tsx"],"sourcesContent":["import { cloneElement, forwardRef } from \"react\";\nimport type {\n ComponentPropsWithoutRef,\n MouseEvent,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n} from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { button } from \"../../styled-system/recipes\";\nimport { Spinner } from \"../layout\";\n\ntype RenderableButtonProps = Record<string, unknown> & {\n children?: ReactNode | undefined;\n className?: string | undefined;\n href?: string | undefined;\n onClick?: MouseEventHandler<HTMLElement> | undefined;\n};\n\nexport interface ButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"color\" | \"children\"> {\n children?: ReactNode;\n variant?: \"outline\" | \"solid\" | \"ghost\" | \"danger\";\n size?: \"sm\" | \"md\" | \"lg\";\n fullWidth?: boolean;\n loading?: boolean;\n startIcon?: ReactNode;\n endIcon?: ReactNode;\n render?: ReactElement<RenderableButtonProps>;\n}\n\nfunction content(children: ReactNode, loading: boolean, startIcon: ReactNode, endIcon: ReactNode) {\n return (\n <>\n {loading ? <Spinner aria-hidden=\"true\" label=\"\" size=\"sm\" /> : startIcon}\n {children}\n {!loading ? endIcon : undefined}\n </>\n );\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n children,\n className,\n variant = \"outline\",\n size = \"md\",\n fullWidth = false,\n loading = false,\n startIcon,\n endIcon,\n disabled,\n onClick: buttonOnClick,\n render,\n type = \"button\",\n ...props\n },\n ref,\n) {\n const buttonClassName = cx(button({ variant, size, fullWidth }), className);\n const childrenWithIcons = content(children, loading, startIcon, endIcon);\n\n if (render) {\n // Keep the render path tolerant of framework-rendered elements and avoid\n // adding a synthetic client handler when the composed element has none.\n const renderedProps = render.props ?? {};\n const renderedOnClick = renderedProps.onClick as MouseEventHandler<HTMLElement> | undefined;\n const handleRenderedClick: MouseEventHandler<HTMLElement> | undefined =\n renderedOnClick || buttonOnClick\n ? disabled || loading\n ? (event) => event.preventDefault()\n : (event) => {\n renderedOnClick?.(event);\n if (!event.defaultPrevented) {\n buttonOnClick?.(event as unknown as MouseEvent<HTMLButtonElement>);\n }\n }\n : undefined;\n\n // A server component can compose Button with a native anchor. Remove its\n // href while disabled/loading instead of attaching a client-only event\n // handler during the server render.\n const renderedHref =\n disabled || loading\n ? render.type === \"a\"\n ? undefined\n : renderedProps.href\n : renderedProps.href;\n\n return cloneElement(render, {\n ...props,\n \"aria-busy\": loading || undefined,\n \"aria-disabled\": disabled || loading || undefined,\n className: cx(buttonClassName, renderedProps.className),\n \"data-jaci-component\": \"button\",\n \"data-slot\": \"button\",\n children: childrenWithIcons,\n onClick: handleRenderedClick,\n ...(render.type === \"a\" && (disabled || loading) ? { href: renderedHref, tabIndex: -1 } : {}),\n });\n }\n\n return (\n <button\n {...props}\n aria-busy={loading || undefined}\n className={buttonClassName}\n data-jaci-component=\"button\"\n data-slot=\"button\"\n disabled={disabled || loading}\n onClick={buttonOnClick}\n ref={ref}\n type={type}\n >\n {childrenWithIcons}\n </button>\n );\n});\n"],"mappings":";;;;;;AAgCA,SAAS,QAAQ,UAAqB,SAAkB,WAAsB,SAAoB;CAChG,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,UAAU,iBAAA,GAAA,kBAAA,IAAA,CAACA,cAAAA,SAAD;GAAS,eAAY;GAAO,OAAM;GAAG,MAAK;EAAM,CAAA,IAAI;EAC9D;EACA,CAAC,UAAU,UAAU,KAAA;CACtB,EAAA,CAAA;AAEN;AAEA,MAAa,UAAA,GAAA,MAAA,WAAA,CAAoD,SAAS,OACxE,EACE,UACA,WACA,UAAU,WACV,OAAO,MACP,YAAY,OACZ,UAAU,OACV,WACA,SACA,UACA,SAAS,eACT,QACA,OAAO,UACP,GAAG,SAEL,KACA;CACA,MAAM,kBAAkBC,WAAAA,GAAGC,eAAAA,OAAO;EAAE;EAAS;EAAM;CAAU,CAAC,GAAG,SAAS;CAC1E,MAAM,oBAAoB,QAAQ,UAAU,SAAS,WAAW,OAAO;CAEvE,IAAI,QAAQ;EAGV,MAAM,gBAAgB,OAAO,SAAS,CAAC;EACvC,MAAM,kBAAkB,cAAc;EACtC,MAAM,sBACJ,mBAAmB,gBACf,YAAY,WACT,UAAU,MAAM,eAAe,KAC/B,UAAU;GACT,kBAAkB,KAAK;GACvB,IAAI,CAAC,MAAM,kBACT,gBAAgB,KAAiD;EAErE,IACF,KAAA;EAKN,MAAM,eACJ,YAAY,UACR,OAAO,SAAS,MACd,KAAA,IACA,cAAc,OAChB,cAAc;EAEpB,QAAA,GAAA,MAAA,aAAA,CAAoB,QAAQ;GAC1B,GAAG;GACH,aAAa,WAAW,KAAA;GACxB,iBAAiB,YAAY,WAAW,KAAA;GACxC,WAAWD,WAAAA,GAAG,iBAAiB,cAAc,SAAS;GACtD,uBAAuB;GACvB,aAAa;GACb,UAAU;GACV,SAAS;GACT,GAAI,OAAO,SAAS,QAAQ,YAAY,WAAW;IAAE,MAAM;IAAc,UAAU;GAAG,IAAI,CAAC;EAC7F,CAAC;CACH;CAEA,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,GAAI;EACJ,aAAW,WAAW,KAAA;EACtB,WAAW;EACX,uBAAoB;EACpB,aAAU;EACV,UAAU,YAAY;EACtB,SAAS;EACJ;EACC;YAEL;CACK,CAAA;AAEZ,CAAC"}
@@ -3,6 +3,7 @@ import { ComponentPropsWithoutRef, MouseEventHandler, ReactElement, ReactNode }
3
3
  type RenderableButtonProps = Record<string, unknown> & {
4
4
  children?: ReactNode | undefined;
5
5
  className?: string | undefined;
6
+ href?: string | undefined;
6
7
  onClick?: MouseEventHandler<HTMLElement> | undefined;
7
8
  };
8
9
  interface ButtonProps extends Omit<ComponentPropsWithoutRef<"button">, "color" | "children"> {
@@ -3,6 +3,7 @@ import { ComponentPropsWithoutRef, MouseEventHandler, ReactElement, ReactNode }
3
3
  type RenderableButtonProps = Record<string, unknown> & {
4
4
  children?: ReactNode | undefined;
5
5
  className?: string | undefined;
6
+ href?: string | undefined;
6
7
  onClick?: MouseEventHandler<HTMLElement> | undefined;
7
8
  };
8
9
  interface ButtonProps extends Omit<ComponentPropsWithoutRef<"button">, "color" | "children"> {
@@ -1,4 +1,3 @@
1
- "use client";
2
1
  import { cx } from "../../styled-system/css/cx.js";
3
2
  import { button } from "../../styled-system/recipes/button.js";
4
3
  import { Spinner } from "../layout/index.js";
@@ -16,7 +15,7 @@ function content(children, loading, startIcon, endIcon) {
16
15
  !loading ? endIcon : void 0
17
16
  ] });
18
17
  }
19
- const Button = forwardRef(function Button({ children, className, variant = "outline", size = "md", fullWidth = false, loading = false, startIcon, endIcon, disabled, render, type = "button", ...props }, ref) {
18
+ const Button = forwardRef(function Button({ children, className, variant = "outline", size = "md", fullWidth = false, loading = false, startIcon, endIcon, disabled, onClick: buttonOnClick, render, type = "button", ...props }, ref) {
20
19
  const buttonClassName = cx(button({
21
20
  variant,
22
21
  size,
@@ -24,7 +23,13 @@ const Button = forwardRef(function Button({ children, className, variant = "outl
24
23
  }), className);
25
24
  const childrenWithIcons = content(children, loading, startIcon, endIcon);
26
25
  if (render) {
27
- const renderedProps = render.props;
26
+ const renderedProps = render.props ?? {};
27
+ const renderedOnClick = renderedProps.onClick;
28
+ const handleRenderedClick = renderedOnClick || buttonOnClick ? disabled || loading ? (event) => event.preventDefault() : (event) => {
29
+ renderedOnClick?.(event);
30
+ if (!event.defaultPrevented) buttonOnClick?.(event);
31
+ } : void 0;
32
+ const renderedHref = disabled || loading ? render.type === "a" ? void 0 : renderedProps.href : renderedProps.href;
28
33
  return cloneElement(render, {
29
34
  ...props,
30
35
  "aria-busy": loading || void 0,
@@ -33,7 +38,11 @@ const Button = forwardRef(function Button({ children, className, variant = "outl
33
38
  "data-jaci-component": "button",
34
39
  "data-slot": "button",
35
40
  children: childrenWithIcons,
36
- onClick: disabled || loading ? void 0 : renderedProps.onClick
41
+ onClick: handleRenderedClick,
42
+ ...render.type === "a" && (disabled || loading) ? {
43
+ href: renderedHref,
44
+ tabIndex: -1
45
+ } : {}
37
46
  });
38
47
  }
39
48
  return /* @__PURE__ */ jsx("button", {
@@ -43,6 +52,7 @@ const Button = forwardRef(function Button({ children, className, variant = "outl
43
52
  "data-jaci-component": "button",
44
53
  "data-slot": "button",
45
54
  disabled: disabled || loading,
55
+ onClick: buttonOnClick,
46
56
  ref,
47
57
  type,
48
58
  children: childrenWithIcons
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":[],"sources":["../../../src/components/button/button.tsx"],"sourcesContent":["\"use client\";\n\nimport { cloneElement, forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, MouseEventHandler, ReactElement, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { button } from \"../../styled-system/recipes\";\nimport { Spinner } from \"../layout\";\n\ntype RenderableButtonProps = Record<string, unknown> & {\n children?: ReactNode | undefined;\n className?: string | undefined;\n onClick?: MouseEventHandler<HTMLElement> | undefined;\n};\n\nexport interface ButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"color\" | \"children\"> {\n children?: ReactNode;\n variant?: \"outline\" | \"solid\" | \"ghost\" | \"danger\";\n size?: \"sm\" | \"md\" | \"lg\";\n fullWidth?: boolean;\n loading?: boolean;\n startIcon?: ReactNode;\n endIcon?: ReactNode;\n render?: ReactElement<RenderableButtonProps>;\n}\n\nfunction content(children: ReactNode, loading: boolean, startIcon: ReactNode, endIcon: ReactNode) {\n return (\n <>\n {loading ? <Spinner aria-hidden=\"true\" label=\"\" size=\"sm\" /> : startIcon}\n {children}\n {!loading ? endIcon : undefined}\n </>\n );\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n children,\n className,\n variant = \"outline\",\n size = \"md\",\n fullWidth = false,\n loading = false,\n startIcon,\n endIcon,\n disabled,\n render,\n type = \"button\",\n ...props\n },\n ref,\n) {\n const buttonClassName = cx(button({ variant, size, fullWidth }), className);\n const childrenWithIcons = content(children, loading, startIcon, endIcon);\n\n if (render) {\n const renderedProps = render.props as {\n className?: string;\n children?: ReactNode;\n onClick?: MouseEventHandler<HTMLElement>;\n };\n\n return cloneElement(render, {\n ...props,\n \"aria-busy\": loading || undefined,\n \"aria-disabled\": disabled || loading || undefined,\n className: cx(buttonClassName, renderedProps.className),\n \"data-jaci-component\": \"button\",\n \"data-slot\": \"button\",\n children: childrenWithIcons,\n onClick: disabled || loading ? undefined : renderedProps.onClick,\n });\n }\n\n return (\n <button\n {...props}\n aria-busy={loading || undefined}\n className={buttonClassName}\n data-jaci-component=\"button\"\n data-slot=\"button\"\n disabled={disabled || loading}\n ref={ref}\n type={type}\n >\n {childrenWithIcons}\n </button>\n );\n});\n"],"mappings":";;;;;;;AA2BA,SAAS,QAAQ,UAAqB,SAAkB,WAAsB,SAAoB;CAChG,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,UAAU,oBAAC,SAAD;GAAS,eAAY;GAAO,OAAM;GAAG,MAAK;EAAM,CAAA,IAAI;EAC9D;EACA,CAAC,UAAU,UAAU,KAAA;CACtB,EAAA,CAAA;AAEN;AAEA,MAAa,SAAS,WAA2C,SAAS,OACxE,EACE,UACA,WACA,UAAU,WACV,OAAO,MACP,YAAY,OACZ,UAAU,OACV,WACA,SACA,UACA,QACA,OAAO,UACP,GAAG,SAEL,KACA;CACA,MAAM,kBAAkB,GAAG,OAAO;EAAE;EAAS;EAAM;CAAU,CAAC,GAAG,SAAS;CAC1E,MAAM,oBAAoB,QAAQ,UAAU,SAAS,WAAW,OAAO;CAEvE,IAAI,QAAQ;EACV,MAAM,gBAAgB,OAAO;EAM7B,OAAO,aAAa,QAAQ;GAC1B,GAAG;GACH,aAAa,WAAW,KAAA;GACxB,iBAAiB,YAAY,WAAW,KAAA;GACxC,WAAW,GAAG,iBAAiB,cAAc,SAAS;GACtD,uBAAuB;GACvB,aAAa;GACb,UAAU;GACV,SAAS,YAAY,UAAU,KAAA,IAAY,cAAc;EAC3D,CAAC;CACH;CAEA,OACE,oBAAC,UAAD;EACE,GAAI;EACJ,aAAW,WAAW,KAAA;EACtB,WAAW;EACX,uBAAoB;EACpB,aAAU;EACV,UAAU,YAAY;EACjB;EACC;YAEL;CACK,CAAA;AAEZ,CAAC"}
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../../src/components/button/button.tsx"],"sourcesContent":["import { cloneElement, forwardRef } from \"react\";\nimport type {\n ComponentPropsWithoutRef,\n MouseEvent,\n MouseEventHandler,\n ReactElement,\n ReactNode,\n} from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { button } from \"../../styled-system/recipes\";\nimport { Spinner } from \"../layout\";\n\ntype RenderableButtonProps = Record<string, unknown> & {\n children?: ReactNode | undefined;\n className?: string | undefined;\n href?: string | undefined;\n onClick?: MouseEventHandler<HTMLElement> | undefined;\n};\n\nexport interface ButtonProps\n extends Omit<ComponentPropsWithoutRef<\"button\">, \"color\" | \"children\"> {\n children?: ReactNode;\n variant?: \"outline\" | \"solid\" | \"ghost\" | \"danger\";\n size?: \"sm\" | \"md\" | \"lg\";\n fullWidth?: boolean;\n loading?: boolean;\n startIcon?: ReactNode;\n endIcon?: ReactNode;\n render?: ReactElement<RenderableButtonProps>;\n}\n\nfunction content(children: ReactNode, loading: boolean, startIcon: ReactNode, endIcon: ReactNode) {\n return (\n <>\n {loading ? <Spinner aria-hidden=\"true\" label=\"\" size=\"sm\" /> : startIcon}\n {children}\n {!loading ? endIcon : undefined}\n </>\n );\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n children,\n className,\n variant = \"outline\",\n size = \"md\",\n fullWidth = false,\n loading = false,\n startIcon,\n endIcon,\n disabled,\n onClick: buttonOnClick,\n render,\n type = \"button\",\n ...props\n },\n ref,\n) {\n const buttonClassName = cx(button({ variant, size, fullWidth }), className);\n const childrenWithIcons = content(children, loading, startIcon, endIcon);\n\n if (render) {\n // Keep the render path tolerant of framework-rendered elements and avoid\n // adding a synthetic client handler when the composed element has none.\n const renderedProps = render.props ?? {};\n const renderedOnClick = renderedProps.onClick as MouseEventHandler<HTMLElement> | undefined;\n const handleRenderedClick: MouseEventHandler<HTMLElement> | undefined =\n renderedOnClick || buttonOnClick\n ? disabled || loading\n ? (event) => event.preventDefault()\n : (event) => {\n renderedOnClick?.(event);\n if (!event.defaultPrevented) {\n buttonOnClick?.(event as unknown as MouseEvent<HTMLButtonElement>);\n }\n }\n : undefined;\n\n // A server component can compose Button with a native anchor. Remove its\n // href while disabled/loading instead of attaching a client-only event\n // handler during the server render.\n const renderedHref =\n disabled || loading\n ? render.type === \"a\"\n ? undefined\n : renderedProps.href\n : renderedProps.href;\n\n return cloneElement(render, {\n ...props,\n \"aria-busy\": loading || undefined,\n \"aria-disabled\": disabled || loading || undefined,\n className: cx(buttonClassName, renderedProps.className),\n \"data-jaci-component\": \"button\",\n \"data-slot\": \"button\",\n children: childrenWithIcons,\n onClick: handleRenderedClick,\n ...(render.type === \"a\" && (disabled || loading) ? { href: renderedHref, tabIndex: -1 } : {}),\n });\n }\n\n return (\n <button\n {...props}\n aria-busy={loading || undefined}\n className={buttonClassName}\n data-jaci-component=\"button\"\n data-slot=\"button\"\n disabled={disabled || loading}\n onClick={buttonOnClick}\n ref={ref}\n type={type}\n >\n {childrenWithIcons}\n </button>\n );\n});\n"],"mappings":";;;;;;AAgCA,SAAS,QAAQ,UAAqB,SAAkB,WAAsB,SAAoB;CAChG,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,UAAU,oBAAC,SAAD;GAAS,eAAY;GAAO,OAAM;GAAG,MAAK;EAAM,CAAA,IAAI;EAC9D;EACA,CAAC,UAAU,UAAU,KAAA;CACtB,EAAA,CAAA;AAEN;AAEA,MAAa,SAAS,WAA2C,SAAS,OACxE,EACE,UACA,WACA,UAAU,WACV,OAAO,MACP,YAAY,OACZ,UAAU,OACV,WACA,SACA,UACA,SAAS,eACT,QACA,OAAO,UACP,GAAG,SAEL,KACA;CACA,MAAM,kBAAkB,GAAG,OAAO;EAAE;EAAS;EAAM;CAAU,CAAC,GAAG,SAAS;CAC1E,MAAM,oBAAoB,QAAQ,UAAU,SAAS,WAAW,OAAO;CAEvE,IAAI,QAAQ;EAGV,MAAM,gBAAgB,OAAO,SAAS,CAAC;EACvC,MAAM,kBAAkB,cAAc;EACtC,MAAM,sBACJ,mBAAmB,gBACf,YAAY,WACT,UAAU,MAAM,eAAe,KAC/B,UAAU;GACT,kBAAkB,KAAK;GACvB,IAAI,CAAC,MAAM,kBACT,gBAAgB,KAAiD;EAErE,IACF,KAAA;EAKN,MAAM,eACJ,YAAY,UACR,OAAO,SAAS,MACd,KAAA,IACA,cAAc,OAChB,cAAc;EAEpB,OAAO,aAAa,QAAQ;GAC1B,GAAG;GACH,aAAa,WAAW,KAAA;GACxB,iBAAiB,YAAY,WAAW,KAAA;GACxC,WAAW,GAAG,iBAAiB,cAAc,SAAS;GACtD,uBAAuB;GACvB,aAAa;GACb,UAAU;GACV,SAAS;GACT,GAAI,OAAO,SAAS,QAAQ,YAAY,WAAW;IAAE,MAAM;IAAc,UAAU;GAAG,IAAI,CAAC;EAC7F,CAAC;CACH;CAEA,OACE,oBAAC,UAAD;EACE,GAAI;EACJ,aAAW,WAAW,KAAA;EACtB,WAAW;EACX,uBAAoB;EACpB,aAAU;EACV,UAAU,YAAY;EACtB,SAAS;EACJ;EACC;YAEL;CACK,CAAA;AAEZ,CAAC"}
@@ -14,6 +14,7 @@ function Finder({ x, y, color, background }) {
14
14
  "data-slot": "qr-code-eye",
15
15
  children: [
16
16
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
17
+ "data-slot": "qr-code-eye-frame",
17
18
  fill: color,
18
19
  height: "7",
19
20
  rx: "1",
@@ -22,6 +23,7 @@ function Finder({ x, y, color, background }) {
22
23
  y
23
24
  }),
24
25
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
26
+ "data-slot": "qr-code-eye-cutout",
25
27
  fill: background,
26
28
  height: "5",
27
29
  rx: "0.75",
@@ -30,6 +32,7 @@ function Finder({ x, y, color, background }) {
30
32
  y: y + 1
31
33
  }),
32
34
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
35
+ "data-slot": "qr-code-eyeball",
33
36
  fill: color,
34
37
  height: "3",
35
38
  rx: "0.5",
@@ -40,66 +43,108 @@ function Finder({ x, y, color, background }) {
40
43
  ]
41
44
  });
42
45
  }
43
- const QRCode = (0, react.forwardRef)(function QRCode({ background = "transparent", className, errorCorrectionLevel = "Q", fallback, foreground = "currentColor", label = "QR code", margin = 2, size = 192, style, value, ...props }, ref) {
46
+ function escapeXml(value) {
47
+ return value.replace(/[&<>'"]/g, (character) => {
48
+ return {
49
+ "&": "&amp;",
50
+ "<": "&lt;",
51
+ ">": "&gt;",
52
+ "'": "&apos;",
53
+ "\"": "&quot;"
54
+ }[character] ?? character;
55
+ });
56
+ }
57
+ function createDownloadSvg({ background, dots, foreground, finderBackground, label, margin, moduleCount }) {
58
+ const viewBoxSize = moduleCount + margin * 2;
59
+ const backgroundMarkup = background === "transparent" ? "" : `<rect fill="${escapeXml(background)}" height="${viewBoxSize}" width="${viewBoxSize}" x="0" y="0"/>`;
60
+ const dotsMarkup = dots.map(([row, column]) => `<circle cx="${margin + column + .5}" cy="${margin + row + .5}" fill="${escapeXml(foreground)}" r="0.43"/>`).join("");
61
+ const finderMarkup = (x, y) => `<g><rect fill="${escapeXml(foreground)}" height="7" rx="1" width="7" x="${x}" y="${y}"/><rect fill="${escapeXml(finderBackground)}" height="5" rx="0.75" width="5" x="${x + 1}" y="${y + 1}"/><rect fill="${escapeXml(foreground)}" height="3" rx="0.5" width="3" x="${x + 2}" y="${y + 2}"/></g>`;
62
+ return `<svg xmlns="http://www.w3.org/2000/svg" aria-label="${escapeXml(label)}" role="img" viewBox="0 0 ${viewBoxSize} ${viewBoxSize}">${backgroundMarkup}${dotsMarkup}${finderMarkup(margin, margin)}${finderMarkup(margin + moduleCount - 7, margin)}${finderMarkup(margin, margin + moduleCount - 7)}</svg>`;
63
+ }
64
+ const QRCode = (0, react.forwardRef)(function QRCode({ background = "#ffffff", className, download = false, downloadLabel = "Download QR code", errorCorrectionLevel = "Q", fallback, foreground = "#18181b", label = "QR code", margin = 2, size = 192, style, value, ...props }, ref) {
44
65
  const safeSize = Number.isFinite(size) && size > 0 ? size : 192;
45
66
  const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;
46
- const finderBackground = background === "transparent" ? "white" : background;
67
+ const finderBackground = background === "transparent" ? "var(--jaci-colors-surface-raised)" : background;
47
68
  try {
48
69
  const code = qrcode.create(value, { errorCorrectionLevel });
49
70
  const moduleCount = code.modules.size;
50
71
  const viewBoxSize = moduleCount + safeMargin * 2;
51
72
  const modules = [];
52
- for (let row = 0; row < moduleCount; row += 1) for (let column = 0; column < moduleCount; column += 1) if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) modules.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
53
- cx: safeMargin + column + .5,
54
- cy: safeMargin + row + .5,
55
- "data-slot": "qr-code-dot",
56
- fill: foreground,
57
- r: "0.43"
58
- }, `${row}-${column}`));
59
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
73
+ const dotCoordinates = [];
74
+ for (let row = 0; row < moduleCount; row += 1) for (let column = 0; column < moduleCount; column += 1) if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {
75
+ dotCoordinates.push([row, column]);
76
+ modules.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
77
+ cx: safeMargin + column + .5,
78
+ cy: safeMargin + row + .5,
79
+ "data-slot": "qr-code-dot",
80
+ fill: foreground,
81
+ r: "0.43"
82
+ }, `${row}-${column}`));
83
+ }
84
+ const downloadHref = download && `data:image/svg+xml;charset=utf-8,${encodeURIComponent(createDownloadSvg({
85
+ background,
86
+ dots: dotCoordinates,
87
+ foreground,
88
+ finderBackground: background === "transparent" ? "#ffffff" : background,
89
+ label,
90
+ margin: safeMargin,
91
+ moduleCount
92
+ }))}`;
93
+ const downloadFilename = typeof download === "string" ? download : "qrcode.svg";
94
+ const visualCode = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
95
+ background !== "transparent" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
96
+ fill: background,
97
+ height: viewBoxSize,
98
+ width: viewBoxSize,
99
+ x: "0",
100
+ y: "0"
101
+ }) : null,
102
+ modules,
103
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
104
+ background: finderBackground,
105
+ color: foreground,
106
+ x: safeMargin,
107
+ y: safeMargin
108
+ }),
109
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
110
+ background: finderBackground,
111
+ color: foreground,
112
+ x: safeMargin + moduleCount - 7,
113
+ y: safeMargin
114
+ }),
115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
116
+ background: finderBackground,
117
+ color: foreground,
118
+ x: safeMargin,
119
+ y: safeMargin + moduleCount - 7
120
+ })
121
+ ] });
122
+ const svg = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
60
123
  ...props,
61
124
  ref,
62
- "aria-label": label,
125
+ "aria-hidden": downloadHref ? true : void 0,
126
+ "aria-label": downloadHref ? void 0 : label,
63
127
  className: require_cx.cx(require_qr_code.qrCode(), className),
64
128
  "data-dot-shape": "circle",
129
+ "data-download": download ? "true" : void 0,
65
130
  "data-eye-shape": "rounded-square",
66
131
  "data-jaci-component": "qr-code",
67
132
  "data-slot": "qr-code",
68
133
  height: safeSize,
69
- role: "img",
134
+ role: downloadHref ? void 0 : "img",
70
135
  style,
71
136
  viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
72
137
  width: safeSize,
73
- children: [
74
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: label }),
75
- background !== "transparent" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
76
- fill: background,
77
- height: viewBoxSize,
78
- width: viewBoxSize,
79
- x: "0",
80
- y: "0"
81
- }) : null,
82
- modules,
83
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
84
- background: finderBackground,
85
- color: foreground,
86
- x: safeMargin,
87
- y: safeMargin
88
- }),
89
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
90
- background: finderBackground,
91
- color: foreground,
92
- x: safeMargin + moduleCount - 7,
93
- y: safeMargin
94
- }),
95
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Finder, {
96
- background: finderBackground,
97
- color: foreground,
98
- x: safeMargin,
99
- y: safeMargin + moduleCount - 7
100
- })
101
- ]
138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("title", { children: label }), visualCode]
102
139
  });
140
+ return downloadHref ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
141
+ "aria-label": downloadLabel,
142
+ "data-jaci-component": "qr-code-download",
143
+ "data-slot": "qr-code-download",
144
+ download: downloadFilename,
145
+ href: downloadHref,
146
+ children: svg
147
+ }) : svg;
103
148
  } catch {
104
149
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
105
150
  "aria-label": label,
@@ -1 +1 @@
1
- {"version":3,"file":"qr-code.cjs","names":["QRCodeEncoder","cx","qrCode"],"sources":["../../../src/components/qr-code/qr-code.tsx"],"sourcesContent":["import * as QRCodeEncoder from \"qrcode\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { qrCode } from \"../../styled-system/recipes\";\n\nexport type QRCodeErrorCorrectionLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\nexport interface QRCodeProps extends Omit<ComponentPropsWithoutRef<\"svg\">, \"children\"> {\n value: string;\n size?: number;\n margin?: number;\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n foreground?: string;\n background?: string;\n label?: string;\n fallback?: ReactNode;\n}\n\nfunction isFinderCell(row: number, column: number, size: number) {\n return (\n (row < 7 && column < 7) || (row < 7 && column >= size - 7) || (row >= size - 7 && column < 7)\n );\n}\n\nfunction Finder({\n x,\n y,\n color,\n background,\n}: {\n x: number;\n y: number;\n color: string;\n background: string;\n}) {\n return (\n <g data-slot=\"qr-code-eye\">\n <rect fill={color} height=\"7\" rx=\"1\" width=\"7\" x={x} y={y} />\n <rect fill={background} height=\"5\" rx=\"0.75\" width=\"5\" x={x + 1} y={y + 1} />\n <rect fill={color} height=\"3\" rx=\"0.5\" width=\"3\" x={x + 2} y={y + 2} />\n </g>\n );\n}\n\nexport const QRCode = forwardRef<SVGSVGElement, QRCodeProps>(function QRCode(\n {\n background = \"transparent\",\n className,\n errorCorrectionLevel = \"Q\",\n fallback,\n foreground = \"currentColor\",\n label = \"QR code\",\n margin = 2,\n size = 192,\n style,\n value,\n ...props\n },\n ref,\n) {\n const safeSize = Number.isFinite(size) && size > 0 ? size : 192;\n const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;\n // Finder patterns need a contrasting quiet area between the frame and the\n // eyeball. Keep the overall SVG transparent by default, but use a white\n // cutout when no background was supplied so the code remains scannable.\n const finderBackground = background === \"transparent\" ? \"white\" : background;\n\n try {\n const code = QRCodeEncoder.create(value, { errorCorrectionLevel });\n const moduleCount = code.modules.size;\n const viewBoxSize = moduleCount + safeMargin * 2;\n const modules: ReactNode[] = [];\n\n for (let row = 0; row < moduleCount; row += 1) {\n for (let column = 0; column < moduleCount; column += 1) {\n if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {\n modules.push(\n <circle\n cx={safeMargin + column + 0.5}\n cy={safeMargin + row + 0.5}\n data-slot=\"qr-code-dot\"\n fill={foreground}\n key={`${row}-${column}`}\n r=\"0.43\"\n />,\n );\n }\n }\n }\n\n return (\n <svg\n {...props}\n ref={ref}\n aria-label={label}\n className={cx(qrCode(), className)}\n data-dot-shape=\"circle\"\n data-eye-shape=\"rounded-square\"\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n height={safeSize}\n role=\"img\"\n style={style}\n viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}\n width={safeSize}\n >\n <title>{label}</title>\n {background !== \"transparent\" ? (\n <rect fill={background} height={viewBoxSize} width={viewBoxSize} x=\"0\" y=\"0\" />\n ) : null}\n {modules}\n <Finder background={finderBackground} color={foreground} x={safeMargin} y={safeMargin} />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin + moduleCount - 7}\n y={safeMargin}\n />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin}\n y={safeMargin + moduleCount - 7}\n />\n </svg>\n );\n } catch {\n return (\n <span\n aria-label={label}\n className={cx(qrCode(), className)}\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n data-status=\"error\"\n role=\"img\"\n >\n {fallback ?? \"Unable to generate QR code\"}\n </span>\n );\n }\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"qr-code.cjs","names":["QRCodeEncoder","cx","qrCode"],"sources":["../../../src/components/qr-code/qr-code.tsx"],"sourcesContent":["import * as QRCodeEncoder from \"qrcode\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { qrCode } from \"../../styled-system/recipes\";\n\nexport type QRCodeErrorCorrectionLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\nexport interface QRCodeProps\n extends Omit<ComponentPropsWithoutRef<\"svg\">, \"children\" | \"download\"> {\n value: string;\n size?: number;\n margin?: number;\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n foreground?: string;\n background?: string;\n label?: string;\n fallback?: ReactNode;\n /** Enables a native SVG download link inside the rendered code. */\n download?: boolean | string;\n downloadLabel?: string;\n}\n\nfunction isFinderCell(row: number, column: number, size: number) {\n return (\n (row < 7 && column < 7) || (row < 7 && column >= size - 7) || (row >= size - 7 && column < 7)\n );\n}\n\nfunction Finder({\n x,\n y,\n color,\n background,\n}: {\n x: number;\n y: number;\n color: string;\n background: string;\n}) {\n return (\n <g data-slot=\"qr-code-eye\">\n <rect data-slot=\"qr-code-eye-frame\" fill={color} height=\"7\" rx=\"1\" width=\"7\" x={x} y={y} />\n <rect\n data-slot=\"qr-code-eye-cutout\"\n fill={background}\n height=\"5\"\n rx=\"0.75\"\n width=\"5\"\n x={x + 1}\n y={y + 1}\n />\n <rect\n data-slot=\"qr-code-eyeball\"\n fill={color}\n height=\"3\"\n rx=\"0.5\"\n width=\"3\"\n x={x + 2}\n y={y + 2}\n />\n </g>\n );\n}\n\nfunction escapeXml(value: string) {\n return value.replace(/[&<>'\"]/g, (character) => {\n const entities: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"'\": \"&apos;\",\n '\"': \"&quot;\",\n };\n return entities[character] ?? character;\n });\n}\n\nfunction createDownloadSvg({\n background,\n dots,\n foreground,\n finderBackground,\n label,\n margin,\n moduleCount,\n}: {\n background: string;\n dots: readonly [number, number][];\n foreground: string;\n finderBackground: string;\n label: string;\n margin: number;\n moduleCount: number;\n}) {\n const viewBoxSize = moduleCount + margin * 2;\n const backgroundMarkup =\n background === \"transparent\"\n ? \"\"\n : `<rect fill=\"${escapeXml(background)}\" height=\"${viewBoxSize}\" width=\"${viewBoxSize}\" x=\"0\" y=\"0\"/>`;\n const dotsMarkup = dots\n .map(\n ([row, column]) =>\n `<circle cx=\"${margin + column + 0.5}\" cy=\"${margin + row + 0.5}\" fill=\"${escapeXml(foreground)}\" r=\"0.43\"/>`,\n )\n .join(\"\");\n const finderMarkup = (x: number, y: number) =>\n `<g><rect fill=\"${escapeXml(foreground)}\" height=\"7\" rx=\"1\" width=\"7\" x=\"${x}\" y=\"${y}\"/><rect fill=\"${escapeXml(finderBackground)}\" height=\"5\" rx=\"0.75\" width=\"5\" x=\"${x + 1}\" y=\"${y + 1}\"/><rect fill=\"${escapeXml(foreground)}\" height=\"3\" rx=\"0.5\" width=\"3\" x=\"${x + 2}\" y=\"${y + 2}\"/></g>`;\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" aria-label=\"${escapeXml(label)}\" role=\"img\" viewBox=\"0 0 ${viewBoxSize} ${viewBoxSize}\">${backgroundMarkup}${dotsMarkup}${finderMarkup(margin, margin)}${finderMarkup(margin + moduleCount - 7, margin)}${finderMarkup(margin, margin + moduleCount - 7)}</svg>`;\n}\n\nexport const QRCode = forwardRef<SVGSVGElement, QRCodeProps>(function QRCode(\n {\n // A QR code needs a deterministic high-contrast base. Inheriting\n // `currentColor` or a transparent surface can make finder layers merge\n // in dark scopes, so the defaults intentionally remain black on white.\n background = \"#ffffff\",\n className,\n download = false,\n downloadLabel = \"Download QR code\",\n errorCorrectionLevel = \"Q\",\n fallback,\n foreground = \"#18181b\",\n label = \"QR code\",\n margin = 2,\n size = 192,\n style,\n value,\n ...props\n },\n ref,\n) {\n const safeSize = Number.isFinite(size) && size > 0 ? size : 192;\n const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;\n const finderBackground =\n background === \"transparent\" ? \"var(--jaci-colors-surface-raised)\" : background;\n\n try {\n const code = QRCodeEncoder.create(value, { errorCorrectionLevel });\n const moduleCount = code.modules.size;\n const viewBoxSize = moduleCount + safeMargin * 2;\n const modules: ReactNode[] = [];\n const dotCoordinates: [number, number][] = [];\n\n for (let row = 0; row < moduleCount; row += 1) {\n for (let column = 0; column < moduleCount; column += 1) {\n if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {\n dotCoordinates.push([row, column]);\n modules.push(\n <circle\n cx={safeMargin + column + 0.5}\n cy={safeMargin + row + 0.5}\n data-slot=\"qr-code-dot\"\n fill={foreground}\n key={`${row}-${column}`}\n r=\"0.43\"\n />,\n );\n }\n }\n }\n\n const downloadHref =\n download &&\n `data:image/svg+xml;charset=utf-8,${encodeURIComponent(\n createDownloadSvg({\n background,\n dots: dotCoordinates,\n foreground,\n finderBackground: background === \"transparent\" ? \"#ffffff\" : background,\n label,\n margin: safeMargin,\n moduleCount,\n }),\n )}`;\n const downloadFilename = typeof download === \"string\" ? download : \"qrcode.svg\";\n const visualCode = (\n <>\n {background !== \"transparent\" ? (\n <rect fill={background} height={viewBoxSize} width={viewBoxSize} x=\"0\" y=\"0\" />\n ) : null}\n {modules}\n <Finder background={finderBackground} color={foreground} x={safeMargin} y={safeMargin} />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin + moduleCount - 7}\n y={safeMargin}\n />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin}\n y={safeMargin + moduleCount - 7}\n />\n </>\n );\n\n const svg = (\n <svg\n {...props}\n ref={ref}\n aria-hidden={downloadHref ? true : undefined}\n aria-label={downloadHref ? undefined : label}\n className={cx(qrCode(), className)}\n data-dot-shape=\"circle\"\n data-download={download ? \"true\" : undefined}\n data-eye-shape=\"rounded-square\"\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n height={safeSize}\n role={downloadHref ? undefined : \"img\"}\n style={style}\n viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}\n width={safeSize}\n >\n <title>{label}</title>\n {visualCode}\n </svg>\n );\n\n return downloadHref ? (\n <a\n aria-label={downloadLabel}\n data-jaci-component=\"qr-code-download\"\n data-slot=\"qr-code-download\"\n download={downloadFilename}\n href={downloadHref}\n >\n {svg}\n </a>\n ) : (\n svg\n );\n } catch {\n return (\n <span\n aria-label={label}\n className={cx(qrCode(), className)}\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n data-status=\"error\"\n role=\"img\"\n >\n {fallback ?? \"Unable to generate QR code\"}\n </span>\n );\n }\n});\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  //#region src/components/qr-code/qr-code.d.ts
3
3
  type QRCodeErrorCorrectionLevel = "L" | "M" | "Q" | "H";
4
- interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children"> {
4
+ interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children" | "download"> {
5
5
  value: string;
6
6
  size?: number;
7
7
  margin?: number;
@@ -10,6 +10,9 @@ interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children">
10
10
  background?: string;
11
11
  label?: string;
12
12
  fallback?: ReactNode;
13
+ /** Enables a native SVG download link inside the rendered code. */
14
+ download?: boolean | string;
15
+ downloadLabel?: string;
13
16
  }
14
17
  declare const QRCode: import("react").ForwardRefExoticComponent<QRCodeProps & import("react").RefAttributes<SVGSVGElement>>;
15
18
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  //#region src/components/qr-code/qr-code.d.ts
3
3
  type QRCodeErrorCorrectionLevel = "L" | "M" | "Q" | "H";
4
- interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children"> {
4
+ interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children" | "download"> {
5
5
  value: string;
6
6
  size?: number;
7
7
  margin?: number;
@@ -10,6 +10,9 @@ interface QRCodeProps extends Omit<ComponentPropsWithoutRef<"svg">, "children">
10
10
  background?: string;
11
11
  label?: string;
12
12
  fallback?: ReactNode;
13
+ /** Enables a native SVG download link inside the rendered code. */
14
+ download?: boolean | string;
15
+ downloadLabel?: string;
13
16
  }
14
17
  declare const QRCode: import("react").ForwardRefExoticComponent<QRCodeProps & import("react").RefAttributes<SVGSVGElement>>;
15
18
  //#endregion
@@ -1,7 +1,7 @@
1
1
  import { cx } from "../../styled-system/css/cx.js";
2
2
  import { qrCode } from "../../styled-system/recipes/qr-code.js";
3
3
  import { forwardRef } from "react";
4
- import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import * as QRCodeEncoder from "qrcode";
6
6
  //#region src/components/qr-code/qr-code.tsx
7
7
  function isFinderCell(row, column, size) {
@@ -12,6 +12,7 @@ function Finder({ x, y, color, background }) {
12
12
  "data-slot": "qr-code-eye",
13
13
  children: [
14
14
  /* @__PURE__ */ jsx("rect", {
15
+ "data-slot": "qr-code-eye-frame",
15
16
  fill: color,
16
17
  height: "7",
17
18
  rx: "1",
@@ -20,6 +21,7 @@ function Finder({ x, y, color, background }) {
20
21
  y
21
22
  }),
22
23
  /* @__PURE__ */ jsx("rect", {
24
+ "data-slot": "qr-code-eye-cutout",
23
25
  fill: background,
24
26
  height: "5",
25
27
  rx: "0.75",
@@ -28,6 +30,7 @@ function Finder({ x, y, color, background }) {
28
30
  y: y + 1
29
31
  }),
30
32
  /* @__PURE__ */ jsx("rect", {
33
+ "data-slot": "qr-code-eyeball",
31
34
  fill: color,
32
35
  height: "3",
33
36
  rx: "0.5",
@@ -38,66 +41,108 @@ function Finder({ x, y, color, background }) {
38
41
  ]
39
42
  });
40
43
  }
41
- const QRCode = forwardRef(function QRCode({ background = "transparent", className, errorCorrectionLevel = "Q", fallback, foreground = "currentColor", label = "QR code", margin = 2, size = 192, style, value, ...props }, ref) {
44
+ function escapeXml(value) {
45
+ return value.replace(/[&<>'"]/g, (character) => {
46
+ return {
47
+ "&": "&amp;",
48
+ "<": "&lt;",
49
+ ">": "&gt;",
50
+ "'": "&apos;",
51
+ "\"": "&quot;"
52
+ }[character] ?? character;
53
+ });
54
+ }
55
+ function createDownloadSvg({ background, dots, foreground, finderBackground, label, margin, moduleCount }) {
56
+ const viewBoxSize = moduleCount + margin * 2;
57
+ const backgroundMarkup = background === "transparent" ? "" : `<rect fill="${escapeXml(background)}" height="${viewBoxSize}" width="${viewBoxSize}" x="0" y="0"/>`;
58
+ const dotsMarkup = dots.map(([row, column]) => `<circle cx="${margin + column + .5}" cy="${margin + row + .5}" fill="${escapeXml(foreground)}" r="0.43"/>`).join("");
59
+ const finderMarkup = (x, y) => `<g><rect fill="${escapeXml(foreground)}" height="7" rx="1" width="7" x="${x}" y="${y}"/><rect fill="${escapeXml(finderBackground)}" height="5" rx="0.75" width="5" x="${x + 1}" y="${y + 1}"/><rect fill="${escapeXml(foreground)}" height="3" rx="0.5" width="3" x="${x + 2}" y="${y + 2}"/></g>`;
60
+ return `<svg xmlns="http://www.w3.org/2000/svg" aria-label="${escapeXml(label)}" role="img" viewBox="0 0 ${viewBoxSize} ${viewBoxSize}">${backgroundMarkup}${dotsMarkup}${finderMarkup(margin, margin)}${finderMarkup(margin + moduleCount - 7, margin)}${finderMarkup(margin, margin + moduleCount - 7)}</svg>`;
61
+ }
62
+ const QRCode = forwardRef(function QRCode({ background = "#ffffff", className, download = false, downloadLabel = "Download QR code", errorCorrectionLevel = "Q", fallback, foreground = "#18181b", label = "QR code", margin = 2, size = 192, style, value, ...props }, ref) {
42
63
  const safeSize = Number.isFinite(size) && size > 0 ? size : 192;
43
64
  const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;
44
- const finderBackground = background === "transparent" ? "white" : background;
65
+ const finderBackground = background === "transparent" ? "var(--jaci-colors-surface-raised)" : background;
45
66
  try {
46
67
  const code = QRCodeEncoder.create(value, { errorCorrectionLevel });
47
68
  const moduleCount = code.modules.size;
48
69
  const viewBoxSize = moduleCount + safeMargin * 2;
49
70
  const modules = [];
50
- for (let row = 0; row < moduleCount; row += 1) for (let column = 0; column < moduleCount; column += 1) if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) modules.push(/* @__PURE__ */ jsx("circle", {
51
- cx: safeMargin + column + .5,
52
- cy: safeMargin + row + .5,
53
- "data-slot": "qr-code-dot",
54
- fill: foreground,
55
- r: "0.43"
56
- }, `${row}-${column}`));
57
- return /* @__PURE__ */ jsxs("svg", {
71
+ const dotCoordinates = [];
72
+ for (let row = 0; row < moduleCount; row += 1) for (let column = 0; column < moduleCount; column += 1) if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {
73
+ dotCoordinates.push([row, column]);
74
+ modules.push(/* @__PURE__ */ jsx("circle", {
75
+ cx: safeMargin + column + .5,
76
+ cy: safeMargin + row + .5,
77
+ "data-slot": "qr-code-dot",
78
+ fill: foreground,
79
+ r: "0.43"
80
+ }, `${row}-${column}`));
81
+ }
82
+ const downloadHref = download && `data:image/svg+xml;charset=utf-8,${encodeURIComponent(createDownloadSvg({
83
+ background,
84
+ dots: dotCoordinates,
85
+ foreground,
86
+ finderBackground: background === "transparent" ? "#ffffff" : background,
87
+ label,
88
+ margin: safeMargin,
89
+ moduleCount
90
+ }))}`;
91
+ const downloadFilename = typeof download === "string" ? download : "qrcode.svg";
92
+ const visualCode = /* @__PURE__ */ jsxs(Fragment, { children: [
93
+ background !== "transparent" ? /* @__PURE__ */ jsx("rect", {
94
+ fill: background,
95
+ height: viewBoxSize,
96
+ width: viewBoxSize,
97
+ x: "0",
98
+ y: "0"
99
+ }) : null,
100
+ modules,
101
+ /* @__PURE__ */ jsx(Finder, {
102
+ background: finderBackground,
103
+ color: foreground,
104
+ x: safeMargin,
105
+ y: safeMargin
106
+ }),
107
+ /* @__PURE__ */ jsx(Finder, {
108
+ background: finderBackground,
109
+ color: foreground,
110
+ x: safeMargin + moduleCount - 7,
111
+ y: safeMargin
112
+ }),
113
+ /* @__PURE__ */ jsx(Finder, {
114
+ background: finderBackground,
115
+ color: foreground,
116
+ x: safeMargin,
117
+ y: safeMargin + moduleCount - 7
118
+ })
119
+ ] });
120
+ const svg = /* @__PURE__ */ jsxs("svg", {
58
121
  ...props,
59
122
  ref,
60
- "aria-label": label,
123
+ "aria-hidden": downloadHref ? true : void 0,
124
+ "aria-label": downloadHref ? void 0 : label,
61
125
  className: cx(qrCode(), className),
62
126
  "data-dot-shape": "circle",
127
+ "data-download": download ? "true" : void 0,
63
128
  "data-eye-shape": "rounded-square",
64
129
  "data-jaci-component": "qr-code",
65
130
  "data-slot": "qr-code",
66
131
  height: safeSize,
67
- role: "img",
132
+ role: downloadHref ? void 0 : "img",
68
133
  style,
69
134
  viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`,
70
135
  width: safeSize,
71
- children: [
72
- /* @__PURE__ */ jsx("title", { children: label }),
73
- background !== "transparent" ? /* @__PURE__ */ jsx("rect", {
74
- fill: background,
75
- height: viewBoxSize,
76
- width: viewBoxSize,
77
- x: "0",
78
- y: "0"
79
- }) : null,
80
- modules,
81
- /* @__PURE__ */ jsx(Finder, {
82
- background: finderBackground,
83
- color: foreground,
84
- x: safeMargin,
85
- y: safeMargin
86
- }),
87
- /* @__PURE__ */ jsx(Finder, {
88
- background: finderBackground,
89
- color: foreground,
90
- x: safeMargin + moduleCount - 7,
91
- y: safeMargin
92
- }),
93
- /* @__PURE__ */ jsx(Finder, {
94
- background: finderBackground,
95
- color: foreground,
96
- x: safeMargin,
97
- y: safeMargin + moduleCount - 7
98
- })
99
- ]
136
+ children: [/* @__PURE__ */ jsx("title", { children: label }), visualCode]
100
137
  });
138
+ return downloadHref ? /* @__PURE__ */ jsx("a", {
139
+ "aria-label": downloadLabel,
140
+ "data-jaci-component": "qr-code-download",
141
+ "data-slot": "qr-code-download",
142
+ download: downloadFilename,
143
+ href: downloadHref,
144
+ children: svg
145
+ }) : svg;
101
146
  } catch {
102
147
  return /* @__PURE__ */ jsx("span", {
103
148
  "aria-label": label,
@@ -1 +1 @@
1
- {"version":3,"file":"qr-code.js","names":[],"sources":["../../../src/components/qr-code/qr-code.tsx"],"sourcesContent":["import * as QRCodeEncoder from \"qrcode\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { qrCode } from \"../../styled-system/recipes\";\n\nexport type QRCodeErrorCorrectionLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\nexport interface QRCodeProps extends Omit<ComponentPropsWithoutRef<\"svg\">, \"children\"> {\n value: string;\n size?: number;\n margin?: number;\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n foreground?: string;\n background?: string;\n label?: string;\n fallback?: ReactNode;\n}\n\nfunction isFinderCell(row: number, column: number, size: number) {\n return (\n (row < 7 && column < 7) || (row < 7 && column >= size - 7) || (row >= size - 7 && column < 7)\n );\n}\n\nfunction Finder({\n x,\n y,\n color,\n background,\n}: {\n x: number;\n y: number;\n color: string;\n background: string;\n}) {\n return (\n <g data-slot=\"qr-code-eye\">\n <rect fill={color} height=\"7\" rx=\"1\" width=\"7\" x={x} y={y} />\n <rect fill={background} height=\"5\" rx=\"0.75\" width=\"5\" x={x + 1} y={y + 1} />\n <rect fill={color} height=\"3\" rx=\"0.5\" width=\"3\" x={x + 2} y={y + 2} />\n </g>\n );\n}\n\nexport const QRCode = forwardRef<SVGSVGElement, QRCodeProps>(function QRCode(\n {\n background = \"transparent\",\n className,\n errorCorrectionLevel = \"Q\",\n fallback,\n foreground = \"currentColor\",\n label = \"QR code\",\n margin = 2,\n size = 192,\n style,\n value,\n ...props\n },\n ref,\n) {\n const safeSize = Number.isFinite(size) && size > 0 ? size : 192;\n const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;\n // Finder patterns need a contrasting quiet area between the frame and the\n // eyeball. Keep the overall SVG transparent by default, but use a white\n // cutout when no background was supplied so the code remains scannable.\n const finderBackground = background === \"transparent\" ? \"white\" : background;\n\n try {\n const code = QRCodeEncoder.create(value, { errorCorrectionLevel });\n const moduleCount = code.modules.size;\n const viewBoxSize = moduleCount + safeMargin * 2;\n const modules: ReactNode[] = [];\n\n for (let row = 0; row < moduleCount; row += 1) {\n for (let column = 0; column < moduleCount; column += 1) {\n if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {\n modules.push(\n <circle\n cx={safeMargin + column + 0.5}\n cy={safeMargin + row + 0.5}\n data-slot=\"qr-code-dot\"\n fill={foreground}\n key={`${row}-${column}`}\n r=\"0.43\"\n />,\n );\n }\n }\n }\n\n return (\n <svg\n {...props}\n ref={ref}\n aria-label={label}\n className={cx(qrCode(), className)}\n data-dot-shape=\"circle\"\n data-eye-shape=\"rounded-square\"\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n height={safeSize}\n role=\"img\"\n style={style}\n viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}\n width={safeSize}\n >\n <title>{label}</title>\n {background !== \"transparent\" ? (\n <rect fill={background} height={viewBoxSize} width={viewBoxSize} x=\"0\" y=\"0\" />\n ) : null}\n {modules}\n <Finder background={finderBackground} color={foreground} x={safeMargin} y={safeMargin} />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin + moduleCount - 7}\n y={safeMargin}\n />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin}\n y={safeMargin + moduleCount - 7}\n />\n </svg>\n );\n } catch {\n return (\n <span\n aria-label={label}\n className={cx(qrCode(), className)}\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n data-status=\"error\"\n role=\"img\"\n >\n {fallback ?? \"Unable to generate QR code\"}\n </span>\n );\n }\n});\n"],"mappings":";;;;;;AAoBA,SAAS,aAAa,KAAa,QAAgB,MAAc;CAC/D,OACG,MAAM,KAAK,SAAS,KAAO,MAAM,KAAK,UAAU,OAAO,KAAO,OAAO,OAAO,KAAK,SAAS;AAE/F;AAEA,SAAS,OAAO,EACd,GACA,GACA,OACA,cAMC;CACD,OACE,qBAAC,KAAD;EAAG,aAAU;YAAb;GACE,oBAAC,QAAD;IAAM,MAAM;IAAO,QAAO;IAAI,IAAG;IAAI,OAAM;IAAO;IAAM;GAAI,CAAA;GAC5D,oBAAC,QAAD;IAAM,MAAM;IAAY,QAAO;IAAI,IAAG;IAAO,OAAM;IAAI,GAAG,IAAI;IAAG,GAAG,IAAI;GAAI,CAAA;GAC5E,oBAAC,QAAD;IAAM,MAAM;IAAO,QAAO;IAAI,IAAG;IAAM,OAAM;IAAI,GAAG,IAAI;IAAG,GAAG,IAAI;GAAI,CAAA;EACrE;;AAEP;AAEA,MAAa,SAAS,WAAuC,SAAS,OACpE,EACE,aAAa,eACb,WACA,uBAAuB,KACvB,UACA,aAAa,gBACb,QAAQ,WACR,SAAS,GACT,OAAO,KACP,OACA,OACA,GAAG,SAEL,KACA;CACA,MAAM,WAAW,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI,OAAO;CAC5D,MAAM,aAAa,OAAO,SAAS,MAAM,KAAK,UAAU,IAAI,KAAK,MAAM,MAAM,IAAI;CAIjF,MAAM,mBAAmB,eAAe,gBAAgB,UAAU;CAElE,IAAI;EACF,MAAM,OAAO,cAAc,OAAO,OAAO,EAAE,qBAAqB,CAAC;EACjE,MAAM,cAAc,KAAK,QAAQ;EACjC,MAAM,cAAc,cAAc,aAAa;EAC/C,MAAM,UAAuB,CAAC;EAE9B,KAAK,IAAI,MAAM,GAAG,MAAM,aAAa,OAAO,GAC1C,KAAK,IAAI,SAAS,GAAG,SAAS,aAAa,UAAU,GACnD,IAAI,KAAK,QAAQ,IAAI,KAAK,MAAM,KAAK,CAAC,aAAa,KAAK,QAAQ,WAAW,GACzE,QAAQ,KACN,oBAAC,UAAD;GACE,IAAI,aAAa,SAAS;GAC1B,IAAI,aAAa,MAAM;GACvB,aAAU;GACV,MAAM;GAEN,GAAE;EACH,GAFM,GAAG,IAAI,GAAG,QAEhB,CACH;EAKN,OACE,qBAAC,OAAD;GACE,GAAI;GACC;GACL,cAAY;GACZ,WAAW,GAAG,OAAO,GAAG,SAAS;GACjC,kBAAe;GACf,kBAAe;GACf,uBAAoB;GACpB,aAAU;GACV,QAAQ;GACR,MAAK;GACE;GACP,SAAS,OAAO,YAAY,GAAG;GAC/B,OAAO;aAbT;IAeE,oBAAC,SAAD,EAAA,UAAQ,MAAa,CAAA;IACpB,eAAe,gBACd,oBAAC,QAAD;KAAM,MAAM;KAAY,QAAQ;KAAa,OAAO;KAAa,GAAE;KAAI,GAAE;IAAK,CAAA,IAC5E;IACH;IACD,oBAAC,QAAD;KAAQ,YAAY;KAAkB,OAAO;KAAY,GAAG;KAAY,GAAG;IAAa,CAAA;IACxF,oBAAC,QAAD;KACE,YAAY;KACZ,OAAO;KACP,GAAG,aAAa,cAAc;KAC9B,GAAG;IACJ,CAAA;IACD,oBAAC,QAAD;KACE,YAAY;KACZ,OAAO;KACP,GAAG;KACH,GAAG,aAAa,cAAc;IAC/B,CAAA;GACE;;CAET,QAAQ;EACN,OACE,oBAAC,QAAD;GACE,cAAY;GACZ,WAAW,GAAG,OAAO,GAAG,SAAS;GACjC,uBAAoB;GACpB,aAAU;GACV,eAAY;GACZ,MAAK;aAEJ,YAAY;EACT,CAAA;CAEV;AACF,CAAC"}
1
+ {"version":3,"file":"qr-code.js","names":[],"sources":["../../../src/components/qr-code/qr-code.tsx"],"sourcesContent":["import * as QRCodeEncoder from \"qrcode\";\nimport { forwardRef } from \"react\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cx } from \"../../styled-system/css\";\nimport { qrCode } from \"../../styled-system/recipes\";\n\nexport type QRCodeErrorCorrectionLevel = \"L\" | \"M\" | \"Q\" | \"H\";\n\nexport interface QRCodeProps\n extends Omit<ComponentPropsWithoutRef<\"svg\">, \"children\" | \"download\"> {\n value: string;\n size?: number;\n margin?: number;\n errorCorrectionLevel?: QRCodeErrorCorrectionLevel;\n foreground?: string;\n background?: string;\n label?: string;\n fallback?: ReactNode;\n /** Enables a native SVG download link inside the rendered code. */\n download?: boolean | string;\n downloadLabel?: string;\n}\n\nfunction isFinderCell(row: number, column: number, size: number) {\n return (\n (row < 7 && column < 7) || (row < 7 && column >= size - 7) || (row >= size - 7 && column < 7)\n );\n}\n\nfunction Finder({\n x,\n y,\n color,\n background,\n}: {\n x: number;\n y: number;\n color: string;\n background: string;\n}) {\n return (\n <g data-slot=\"qr-code-eye\">\n <rect data-slot=\"qr-code-eye-frame\" fill={color} height=\"7\" rx=\"1\" width=\"7\" x={x} y={y} />\n <rect\n data-slot=\"qr-code-eye-cutout\"\n fill={background}\n height=\"5\"\n rx=\"0.75\"\n width=\"5\"\n x={x + 1}\n y={y + 1}\n />\n <rect\n data-slot=\"qr-code-eyeball\"\n fill={color}\n height=\"3\"\n rx=\"0.5\"\n width=\"3\"\n x={x + 2}\n y={y + 2}\n />\n </g>\n );\n}\n\nfunction escapeXml(value: string) {\n return value.replace(/[&<>'\"]/g, (character) => {\n const entities: Record<string, string> = {\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n \"'\": \"&apos;\",\n '\"': \"&quot;\",\n };\n return entities[character] ?? character;\n });\n}\n\nfunction createDownloadSvg({\n background,\n dots,\n foreground,\n finderBackground,\n label,\n margin,\n moduleCount,\n}: {\n background: string;\n dots: readonly [number, number][];\n foreground: string;\n finderBackground: string;\n label: string;\n margin: number;\n moduleCount: number;\n}) {\n const viewBoxSize = moduleCount + margin * 2;\n const backgroundMarkup =\n background === \"transparent\"\n ? \"\"\n : `<rect fill=\"${escapeXml(background)}\" height=\"${viewBoxSize}\" width=\"${viewBoxSize}\" x=\"0\" y=\"0\"/>`;\n const dotsMarkup = dots\n .map(\n ([row, column]) =>\n `<circle cx=\"${margin + column + 0.5}\" cy=\"${margin + row + 0.5}\" fill=\"${escapeXml(foreground)}\" r=\"0.43\"/>`,\n )\n .join(\"\");\n const finderMarkup = (x: number, y: number) =>\n `<g><rect fill=\"${escapeXml(foreground)}\" height=\"7\" rx=\"1\" width=\"7\" x=\"${x}\" y=\"${y}\"/><rect fill=\"${escapeXml(finderBackground)}\" height=\"5\" rx=\"0.75\" width=\"5\" x=\"${x + 1}\" y=\"${y + 1}\"/><rect fill=\"${escapeXml(foreground)}\" height=\"3\" rx=\"0.5\" width=\"3\" x=\"${x + 2}\" y=\"${y + 2}\"/></g>`;\n\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" aria-label=\"${escapeXml(label)}\" role=\"img\" viewBox=\"0 0 ${viewBoxSize} ${viewBoxSize}\">${backgroundMarkup}${dotsMarkup}${finderMarkup(margin, margin)}${finderMarkup(margin + moduleCount - 7, margin)}${finderMarkup(margin, margin + moduleCount - 7)}</svg>`;\n}\n\nexport const QRCode = forwardRef<SVGSVGElement, QRCodeProps>(function QRCode(\n {\n // A QR code needs a deterministic high-contrast base. Inheriting\n // `currentColor` or a transparent surface can make finder layers merge\n // in dark scopes, so the defaults intentionally remain black on white.\n background = \"#ffffff\",\n className,\n download = false,\n downloadLabel = \"Download QR code\",\n errorCorrectionLevel = \"Q\",\n fallback,\n foreground = \"#18181b\",\n label = \"QR code\",\n margin = 2,\n size = 192,\n style,\n value,\n ...props\n },\n ref,\n) {\n const safeSize = Number.isFinite(size) && size > 0 ? size : 192;\n const safeMargin = Number.isFinite(margin) && margin >= 0 ? Math.floor(margin) : 2;\n const finderBackground =\n background === \"transparent\" ? \"var(--jaci-colors-surface-raised)\" : background;\n\n try {\n const code = QRCodeEncoder.create(value, { errorCorrectionLevel });\n const moduleCount = code.modules.size;\n const viewBoxSize = moduleCount + safeMargin * 2;\n const modules: ReactNode[] = [];\n const dotCoordinates: [number, number][] = [];\n\n for (let row = 0; row < moduleCount; row += 1) {\n for (let column = 0; column < moduleCount; column += 1) {\n if (code.modules.get(row, column) && !isFinderCell(row, column, moduleCount)) {\n dotCoordinates.push([row, column]);\n modules.push(\n <circle\n cx={safeMargin + column + 0.5}\n cy={safeMargin + row + 0.5}\n data-slot=\"qr-code-dot\"\n fill={foreground}\n key={`${row}-${column}`}\n r=\"0.43\"\n />,\n );\n }\n }\n }\n\n const downloadHref =\n download &&\n `data:image/svg+xml;charset=utf-8,${encodeURIComponent(\n createDownloadSvg({\n background,\n dots: dotCoordinates,\n foreground,\n finderBackground: background === \"transparent\" ? \"#ffffff\" : background,\n label,\n margin: safeMargin,\n moduleCount,\n }),\n )}`;\n const downloadFilename = typeof download === \"string\" ? download : \"qrcode.svg\";\n const visualCode = (\n <>\n {background !== \"transparent\" ? (\n <rect fill={background} height={viewBoxSize} width={viewBoxSize} x=\"0\" y=\"0\" />\n ) : null}\n {modules}\n <Finder background={finderBackground} color={foreground} x={safeMargin} y={safeMargin} />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin + moduleCount - 7}\n y={safeMargin}\n />\n <Finder\n background={finderBackground}\n color={foreground}\n x={safeMargin}\n y={safeMargin + moduleCount - 7}\n />\n </>\n );\n\n const svg = (\n <svg\n {...props}\n ref={ref}\n aria-hidden={downloadHref ? true : undefined}\n aria-label={downloadHref ? undefined : label}\n className={cx(qrCode(), className)}\n data-dot-shape=\"circle\"\n data-download={download ? \"true\" : undefined}\n data-eye-shape=\"rounded-square\"\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n height={safeSize}\n role={downloadHref ? undefined : \"img\"}\n style={style}\n viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}\n width={safeSize}\n >\n <title>{label}</title>\n {visualCode}\n </svg>\n );\n\n return downloadHref ? (\n <a\n aria-label={downloadLabel}\n data-jaci-component=\"qr-code-download\"\n data-slot=\"qr-code-download\"\n download={downloadFilename}\n href={downloadHref}\n >\n {svg}\n </a>\n ) : (\n svg\n );\n } catch {\n return (\n <span\n aria-label={label}\n className={cx(qrCode(), className)}\n data-jaci-component=\"qr-code\"\n data-slot=\"qr-code\"\n data-status=\"error\"\n role=\"img\"\n >\n {fallback ?? \"Unable to generate QR code\"}\n </span>\n );\n }\n});\n"],"mappings":";;;;;;AAwBA,SAAS,aAAa,KAAa,QAAgB,MAAc;CAC/D,OACG,MAAM,KAAK,SAAS,KAAO,MAAM,KAAK,UAAU,OAAO,KAAO,OAAO,OAAO,KAAK,SAAS;AAE/F;AAEA,SAAS,OAAO,EACd,GACA,GACA,OACA,cAMC;CACD,OACE,qBAAC,KAAD;EAAG,aAAU;YAAb;GACE,oBAAC,QAAD;IAAM,aAAU;IAAoB,MAAM;IAAO,QAAO;IAAI,IAAG;IAAI,OAAM;IAAO;IAAM;GAAI,CAAA;GAC1F,oBAAC,QAAD;IACE,aAAU;IACV,MAAM;IACN,QAAO;IACP,IAAG;IACH,OAAM;IACN,GAAG,IAAI;IACP,GAAG,IAAI;GACR,CAAA;GACD,oBAAC,QAAD;IACE,aAAU;IACV,MAAM;IACN,QAAO;IACP,IAAG;IACH,OAAM;IACN,GAAG,IAAI;IACP,GAAG,IAAI;GACR,CAAA;EACA;;AAEP;AAEA,SAAS,UAAU,OAAe;CAChC,OAAO,MAAM,QAAQ,aAAa,cAAc;EAQ9C,OAAO;GANL,KAAK;GACL,KAAK;GACL,KAAK;GACL,KAAK;GACL,MAAK;EAEO,EAAE,cAAc;CAChC,CAAC;AACH;AAEA,SAAS,kBAAkB,EACzB,YACA,MACA,YACA,kBACA,OACA,QACA,eASC;CACD,MAAM,cAAc,cAAc,SAAS;CAC3C,MAAM,mBACJ,eAAe,gBACX,KACA,eAAe,UAAU,UAAU,EAAE,YAAY,YAAY,WAAW,YAAY;CAC1F,MAAM,aAAa,KAChB,KACE,CAAC,KAAK,YACL,eAAe,SAAS,SAAS,GAAI,QAAQ,SAAS,MAAM,GAAI,UAAU,UAAU,UAAU,EAAE,aACpG,CAAC,CACA,KAAK,EAAE;CACV,MAAM,gBAAgB,GAAW,MAC/B,kBAAkB,UAAU,UAAU,EAAE,mCAAmC,EAAE,OAAO,EAAE,iBAAiB,UAAU,gBAAgB,EAAE,sCAAsC,IAAI,EAAE,OAAO,IAAI,EAAE,iBAAiB,UAAU,UAAU,EAAE,qCAAqC,IAAI,EAAE,OAAO,IAAI,EAAE;CAE7R,OAAO,uDAAuD,UAAU,KAAK,EAAE,4BAA4B,YAAY,GAAG,YAAY,IAAI,mBAAmB,aAAa,aAAa,QAAQ,MAAM,IAAI,aAAa,SAAS,cAAc,GAAG,MAAM,IAAI,aAAa,QAAQ,SAAS,cAAc,CAAC,EAAE;AAC3S;AAEA,MAAa,SAAS,WAAuC,SAAS,OACpE,EAIE,aAAa,WACb,WACA,WAAW,OACX,gBAAgB,oBAChB,uBAAuB,KACvB,UACA,aAAa,WACb,QAAQ,WACR,SAAS,GACT,OAAO,KACP,OACA,OACA,GAAG,SAEL,KACA;CACA,MAAM,WAAW,OAAO,SAAS,IAAI,KAAK,OAAO,IAAI,OAAO;CAC5D,MAAM,aAAa,OAAO,SAAS,MAAM,KAAK,UAAU,IAAI,KAAK,MAAM,MAAM,IAAI;CACjF,MAAM,mBACJ,eAAe,gBAAgB,sCAAsC;CAEvE,IAAI;EACF,MAAM,OAAO,cAAc,OAAO,OAAO,EAAE,qBAAqB,CAAC;EACjE,MAAM,cAAc,KAAK,QAAQ;EACjC,MAAM,cAAc,cAAc,aAAa;EAC/C,MAAM,UAAuB,CAAC;EAC9B,MAAM,iBAAqC,CAAC;EAE5C,KAAK,IAAI,MAAM,GAAG,MAAM,aAAa,OAAO,GAC1C,KAAK,IAAI,SAAS,GAAG,SAAS,aAAa,UAAU,GACnD,IAAI,KAAK,QAAQ,IAAI,KAAK,MAAM,KAAK,CAAC,aAAa,KAAK,QAAQ,WAAW,GAAG;GAC5E,eAAe,KAAK,CAAC,KAAK,MAAM,CAAC;GACjC,QAAQ,KACN,oBAAC,UAAD;IACE,IAAI,aAAa,SAAS;IAC1B,IAAI,aAAa,MAAM;IACvB,aAAU;IACV,MAAM;IAEN,GAAE;GACH,GAFM,GAAG,IAAI,GAAG,QAEhB,CACH;EACF;EAIJ,MAAM,eACJ,YACA,oCAAoC,mBAClC,kBAAkB;GAChB;GACA,MAAM;GACN;GACA,kBAAkB,eAAe,gBAAgB,YAAY;GAC7D;GACA,QAAQ;GACR;EACF,CAAC,CACH;EACF,MAAM,mBAAmB,OAAO,aAAa,WAAW,WAAW;EACnE,MAAM,aACJ,qBAAA,UAAA,EAAA,UAAA;GACG,eAAe,gBACd,oBAAC,QAAD;IAAM,MAAM;IAAY,QAAQ;IAAa,OAAO;IAAa,GAAE;IAAI,GAAE;GAAK,CAAA,IAC5E;GACH;GACD,oBAAC,QAAD;IAAQ,YAAY;IAAkB,OAAO;IAAY,GAAG;IAAY,GAAG;GAAa,CAAA;GACxF,oBAAC,QAAD;IACE,YAAY;IACZ,OAAO;IACP,GAAG,aAAa,cAAc;IAC9B,GAAG;GACJ,CAAA;GACD,oBAAC,QAAD;IACE,YAAY;IACZ,OAAO;IACP,GAAG;IACH,GAAG,aAAa,cAAc;GAC/B,CAAA;EACD,EAAA,CAAA;EAGJ,MAAM,MACJ,qBAAC,OAAD;GACE,GAAI;GACC;GACL,eAAa,eAAe,OAAO,KAAA;GACnC,cAAY,eAAe,KAAA,IAAY;GACvC,WAAW,GAAG,OAAO,GAAG,SAAS;GACjC,kBAAe;GACf,iBAAe,WAAW,SAAS,KAAA;GACnC,kBAAe;GACf,uBAAoB;GACpB,aAAU;GACV,QAAQ;GACR,MAAM,eAAe,KAAA,IAAY;GAC1B;GACP,SAAS,OAAO,YAAY,GAAG;GAC/B,OAAO;aAfT,CAiBE,oBAAC,SAAD,EAAA,UAAQ,MAAa,CAAA,GACpB,UACE;;EAGP,OAAO,eACL,oBAAC,KAAD;GACE,cAAY;GACZ,uBAAoB;GACpB,aAAU;GACV,UAAU;GACV,MAAM;aAEL;EACA,CAAA,IAEH;CAEJ,QAAQ;EACN,OACE,oBAAC,QAAD;GACE,cAAY;GACZ,WAAW,GAAG,OAAO,GAAG,SAAS;GACjC,uBAAoB;GACpB,aAAU;GACV,eAAY;GACZ,MAAK;aAEJ,YAAY;EACT,CAAA;CAEV;AACF,CAAC"}
package/dist/styles.css CHANGED
@@ -2053,6 +2053,8 @@
2053
2053
  overflow: hidden;
2054
2054
  background-color: var(--jaci-colors-surface-raised);
2055
2055
  color: var(--jaci-colors-fg-default);
2056
+ min-width: var(--jaci-sizes-0);
2057
+ width: 100%;
2056
2058
  }
2057
2059
 
2058
2060
  .jaci-accordion__item {
@@ -10872,12 +10874,12 @@
10872
10874
 
10873
10875
  @layer utilities{
10874
10876
 
10875
- .jaci-bg_white {
10876
- background: var(--jaci-colors-white);
10877
+ .jaci-bg_var\(--jaci-colors-surface-raised\) {
10878
+ background: var(--jaci-colors-surface-raised);
10877
10879
  }
10878
10880
 
10879
- .jaci-bg_transparent {
10880
- background: var(--jaci-colors-transparent);
10881
+ .jaci-bg_\#ffffff {
10882
+ background: #ffffff;
10881
10883
  }
10882
10884
 
10883
10885
  .jaci-gap_8px {
@@ -10896,8 +10898,8 @@
10896
10898
  page: 1px;
10897
10899
  }
10898
10900
 
10899
- .jaci-c_currentColor {
10900
- color: currentColor;
10901
+ .jaci-c_\#18181b {
10902
+ color: #18181b;
10901
10903
  }
10902
10904
 
10903
10905
  .jaci-translate-x_2 {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jaci-ui",
3
3
  "description": "Accessible, themeable React components by Jaci UI",
4
- "version": "0.9.1",
4
+ "version": "0.9.3",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Richard Borges",
@@ -86,13 +86,13 @@
86
86
  "access": "public"
87
87
  },
88
88
  "devDependencies": {
89
- "@pandacss/dev": "^1.11.4",
89
+ "@pandacss/dev": "^1.11.5",
90
90
  "@types/qrcode": "^1.5.6",
91
- "@types/react": "^19.2.17",
92
- "@types/react-dom": "^19.2.3",
91
+ "@types/react": "^19.2.18",
92
+ "@types/react-dom": "^19.2.4",
93
93
  "jsdom": "^27.4.0",
94
- "react": "^19.2.7",
95
- "react-dom": "^19.2.7",
94
+ "react": "^19.2.8",
95
+ "react-dom": "^19.2.8",
96
96
  "tsdown": "^0.22.4"
97
97
  },
98
98
  "scripts": {