@techzyapp/ui-library 1.0.0 → 1.0.2
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/README.md +134 -0
- package/dist/Blockquote-CNIuJFHq.js +46 -0
- package/dist/Blockquote-CNIuJFHq.js.map +1 -0
- package/dist/Blockquote-CxHwOecc.cjs +2 -0
- package/dist/Blockquote-CxHwOecc.cjs.map +1 -0
- package/dist/Code-CUdpWlY5.cjs +2 -0
- package/dist/Code-CUdpWlY5.cjs.map +1 -0
- package/dist/Code-DaEpXHfF.js +26 -0
- package/dist/Code-DaEpXHfF.js.map +1 -0
- package/dist/CodeBlock-BWxQyKRo.js +96 -0
- package/dist/CodeBlock-BWxQyKRo.js.map +1 -0
- package/dist/CodeBlock-CgXOjKS-.cjs +2 -0
- package/dist/CodeBlock-CgXOjKS-.cjs.map +1 -0
- package/dist/Masonry--bT8SICO.js +80 -0
- package/dist/Masonry--bT8SICO.js.map +1 -0
- package/dist/Masonry-DlD-rIQX.cjs +2 -0
- package/dist/Masonry-DlD-rIQX.cjs.map +1 -0
- package/dist/components/Blockquote/Blockquote.d.cts +25 -0
- package/dist/components/Blockquote/Blockquote.d.ts +25 -0
- package/dist/components/Blockquote/index.cjs +2 -0
- package/dist/components/Blockquote/index.cjs.map +1 -0
- package/dist/components/Blockquote/index.d.cts +2 -0
- package/dist/components/Blockquote/index.d.ts +2 -0
- package/dist/components/Blockquote/index.js +6 -0
- package/dist/components/Blockquote/index.js.map +1 -0
- package/dist/components/Code/Code.d.cts +21 -0
- package/dist/components/Code/Code.d.ts +21 -0
- package/dist/components/Code/index.cjs +2 -0
- package/dist/components/Code/index.cjs.map +1 -0
- package/dist/components/Code/index.d.cts +2 -0
- package/dist/components/Code/index.d.ts +2 -0
- package/dist/components/Code/index.js +6 -0
- package/dist/components/Code/index.js.map +1 -0
- package/dist/components/CodeBlock/CodeBlock.d.cts +10 -6
- package/dist/components/CodeBlock/CodeBlock.d.ts +10 -6
- package/dist/components/CodeBlock/index.cjs +1 -1
- package/dist/components/CodeBlock/index.js +1 -1
- package/dist/components/Masonry/Masonry.d.cts +23 -0
- package/dist/components/Masonry/Masonry.d.ts +23 -0
- package/dist/components/Masonry/index.cjs +2 -0
- package/dist/components/Masonry/index.cjs.map +1 -0
- package/dist/components/Masonry/index.d.cts +2 -0
- package/dist/components/Masonry/index.d.ts +2 -0
- package/dist/components/Masonry/index.js +6 -0
- package/dist/components/Masonry/index.js.map +1 -0
- package/dist/components/index.d.cts +3 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/entries/components.cjs +1 -1
- package/dist/entries/components.js +130 -124
- package/dist/entries/components.js.map +1 -1
- package/dist/helpers/translations.d.cts +1 -1
- package/dist/helpers/translations.d.ts +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +144 -138
- package/dist/index.js.map +1 -1
- package/dist/translations-CXEeeUDL.cjs.map +1 -1
- package/dist/translations-D15tFHM1.js.map +1 -1
- package/package.json +1 -1
- package/dist/CodeBlock-DOk5wekc.cjs +0 -2
- package/dist/CodeBlock-DOk5wekc.cjs.map +0 -1
- package/dist/CodeBlock-n3KExtTO.js +0 -104
- package/dist/CodeBlock-n3KExtTO.js.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# @techzyapp/ui-library
|
|
2
|
+
|
|
3
|
+
**Techzy UI** — a reusable, framework-agnostic React component library and the shared UI foundation for
|
|
4
|
+
Techzy admin panels. Ships ~60 components plus a batteries-included admin shell (sidebar + header +
|
|
5
|
+
routing), theming, and built-in UI localization.
|
|
6
|
+
|
|
7
|
+
- **React 19** (peer `>=18`), TypeScript (strict), CSS Modules, dual **ESM/CJS**.
|
|
8
|
+
- **Theming** via `ConfigProvider` — 9 palette colors, light/dark, per-mode accent, custom fonts.
|
|
9
|
+
- **i18n out of the box** — the panel ships translated in **7 languages** (en · ka · es · de · it · fr · ru);
|
|
10
|
+
just list the ones you want.
|
|
11
|
+
- **Batteries included** — `Table`, `RichTextEditor`, `FileUploader`, date/time pickers, the admin shell, and more.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i @techzyapp/ui-library
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`react` / `react-dom` are required peers. Everything else is an **optional peer** — install only what the
|
|
22
|
+
features you use need:
|
|
23
|
+
|
|
24
|
+
| Feature | Peer dependency |
|
|
25
|
+
| -------------------------------------- | --------------------------------------------------------------------------- |
|
|
26
|
+
| Forms (`useForm`) | `zod` |
|
|
27
|
+
| Admin shell (`RootLayout` / `Sidebar`) | `@tanstack/react-router` |
|
|
28
|
+
| `Table` | `@tanstack/react-table` |
|
|
29
|
+
| Date/time pickers | `dayjs` |
|
|
30
|
+
| `RichTextEditor` | `lexical` + `@lexical/*` |
|
|
31
|
+
| `FileUploader` | `react-dropzone`, `@dnd-kit/*`, `@formkit/auto-animate`, `react-image-crop` |
|
|
32
|
+
| `Table` row reorder | `@dnd-kit/*` |
|
|
33
|
+
| `CodeBlock` | `shiki` |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Scaffold a whole project (fastest)
|
|
38
|
+
|
|
39
|
+
Building a new admin panel from scratch? The repo ships **[`CONSUMER.md`](./CONSUMER.md)** — a drop-in
|
|
40
|
+
guide for Claude Code. Copy it into an **empty folder** as `CLAUDE.md`, tell Claude Code to _"set up the
|
|
41
|
+
project,"_ and it scaffolds a clean **Vite + TanStack Router** app wired to this library — theming,
|
|
42
|
+
routing, and a demo **Example Page** — with the boilerplate removed. Keep the file at the repo root
|
|
43
|
+
afterwards as the assistant's reference for building pages, forms, and the admin shell.
|
|
44
|
+
|
|
45
|
+
For a manual / existing-project setup, follow the steps below.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Setup
|
|
50
|
+
|
|
51
|
+
Import the two stylesheets once at your app entry, then wrap the app in `ConfigProvider`:
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
// main.tsx
|
|
55
|
+
import '@techzyapp/ui-library/css/reset.css'
|
|
56
|
+
import '@techzyapp/ui-library/css/styles.css'
|
|
57
|
+
|
|
58
|
+
import { ConfigProvider, type Config } from '@techzyapp/ui-library/theme'
|
|
59
|
+
|
|
60
|
+
const config: Config = {
|
|
61
|
+
theme: { mode: 'light' },
|
|
62
|
+
// the panel ships en + ka + es + de + it + fr + ru — listing them is all it takes
|
|
63
|
+
i18n: {
|
|
64
|
+
languages: [
|
|
65
|
+
{ code: 'en-US', label: 'English' },
|
|
66
|
+
{ code: 'ka-GE', label: 'ქართული' },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function App() {
|
|
72
|
+
return <ConfigProvider config={config}>{/* your app / router */}</ConfigProvider>
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> `ConfigProvider` injects the theme's color variables before first paint, so it must wrap anything that
|
|
77
|
+
> uses the components.
|
|
78
|
+
|
|
79
|
+
## Quick example
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { Button, Card, TextField } from '@techzyapp/ui-library/components'
|
|
83
|
+
import { Icon } from '@techzyapp/ui-library/icons'
|
|
84
|
+
|
|
85
|
+
function SignInCard() {
|
|
86
|
+
return (
|
|
87
|
+
<Card title="Sign in" icon="User">
|
|
88
|
+
<TextField label="Email" placeholder="you@techzy.app" />
|
|
89
|
+
<TextField label="Password" type="password" />
|
|
90
|
+
<Button fullWidth startIcon={<Icon name="Login" />}>
|
|
91
|
+
Continue
|
|
92
|
+
</Button>
|
|
93
|
+
</Card>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Localizing your own text
|
|
99
|
+
|
|
100
|
+
`useTranslations()` binds your app's copy to the same language switcher the panel uses:
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { useTranslations } from '@techzyapp/ui-library/i18n'
|
|
104
|
+
|
|
105
|
+
// config.i18n.appMessages = { en: { 'welcome': 'Welcome' }, ka: { 'welcome': 'მოგესალმებით' } }
|
|
106
|
+
const t = useTranslations()
|
|
107
|
+
;<h1>{t('welcome')}</h1>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Subpath exports
|
|
113
|
+
|
|
114
|
+
Import from the scoped surfaces (the root `.` re-exports everything too):
|
|
115
|
+
|
|
116
|
+
| Import | What's in it |
|
|
117
|
+
| --------------------------------------------------------- | --------------------------------------------------------------------------------- |
|
|
118
|
+
| `@techzyapp/ui-library/components` | every component + the admin shell + `Form` |
|
|
119
|
+
| `@techzyapp/ui-library/components/<Name>` | a single component |
|
|
120
|
+
| `@techzyapp/ui-library/hooks` | `useDisclosure`, `useMediaQuery`, `useForm`, `useLockBodyScroll`, `useAccessKeys` |
|
|
121
|
+
| `@techzyapp/ui-library/theme` | `ConfigProvider`, `useTheme`, config types, `applyTheme` |
|
|
122
|
+
| `@techzyapp/ui-library/i18n` | `useT`, `useTranslations`, `useLanguage` + message types |
|
|
123
|
+
| `@techzyapp/ui-library/icons` | `Icon`, `IconName`, `ICON_NAMES` |
|
|
124
|
+
| `@techzyapp/ui-library/helpers` | RBAC + translation + `Table` query helpers |
|
|
125
|
+
| `@techzyapp/ui-library/css/reset.css` · `/css/styles.css` | the two stylesheets |
|
|
126
|
+
|
|
127
|
+
> Subpath imports need `moduleResolution: "bundler"` or `"node16"` in your `tsconfig`; the root import
|
|
128
|
+
> works on classic `"node"` too.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT © Techzy
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsxs as e, jsx as t } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as b } from "react";
|
|
3
|
+
import { clsx as f } from "clsx";
|
|
4
|
+
const k = "_blockquote_11r93_3", u = "_filled_11r93_16", p = "_plain_11r93_19", q = "_sm_11r93_24", h = "_md_11r93_27", y = "_lg_11r93_30", g = "_mark_11r93_35", N = "_body_11r93_45", x = "_content_11r93_54", v = "_cite_11r93_59", o = {
|
|
5
|
+
blockquote: k,
|
|
6
|
+
filled: u,
|
|
7
|
+
plain: p,
|
|
8
|
+
sm: q,
|
|
9
|
+
md: h,
|
|
10
|
+
lg: y,
|
|
11
|
+
mark: g,
|
|
12
|
+
body: N,
|
|
13
|
+
content: x,
|
|
14
|
+
cite: v
|
|
15
|
+
}, R = b(function({
|
|
16
|
+
variant: l = "filled",
|
|
17
|
+
color: r = "accent",
|
|
18
|
+
size: n = "md",
|
|
19
|
+
cite: c,
|
|
20
|
+
quoteMark: s = !1,
|
|
21
|
+
className: _,
|
|
22
|
+
style: a,
|
|
23
|
+
children: i,
|
|
24
|
+
...d
|
|
25
|
+
}, m) {
|
|
26
|
+
return /* @__PURE__ */ e(
|
|
27
|
+
"blockquote",
|
|
28
|
+
{
|
|
29
|
+
ref: m,
|
|
30
|
+
className: f(o.blockquote, o[l], o[n], _),
|
|
31
|
+
style: { "--tz-btn-rgb": `var(--tz-color-${r}-rgb)`, ...a },
|
|
32
|
+
...d,
|
|
33
|
+
children: [
|
|
34
|
+
s && /* @__PURE__ */ t("span", { className: o.mark, "aria-hidden": !0, children: "“" }),
|
|
35
|
+
/* @__PURE__ */ e("div", { className: o.body, children: [
|
|
36
|
+
/* @__PURE__ */ t("div", { className: o.content, children: i }),
|
|
37
|
+
c != null && /* @__PURE__ */ t("footer", { className: o.cite, children: /* @__PURE__ */ t("cite", { children: c }) })
|
|
38
|
+
] })
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
export {
|
|
44
|
+
R as B
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=Blockquote-CNIuJFHq.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Blockquote-CNIuJFHq.js","sources":["../src/components/Blockquote/Blockquote.tsx"],"sourcesContent":["import {\n forwardRef,\n type BlockquoteHTMLAttributes,\n type CSSProperties,\n type ReactNode,\n} from 'react'\nimport { clsx } from 'clsx'\nimport type { ThemeColor } from '../../theme'\nimport styles from './Blockquote.module.css'\n\nexport type BlockquoteVariant = 'filled' | 'plain'\nexport type BlockquoteSize = 'sm' | 'md' | 'lg'\n\nexport interface BlockquoteProps extends Omit<\n BlockquoteHTMLAttributes<HTMLQuoteElement>,\n 'color' | 'cite'\n> {\n /** Tint style — `filled` (soft tinted background + accent bar) or `plain` (accent bar only). Defaults to `filled`. */\n variant?: BlockquoteVariant\n /** Theme palette token that tints the accent bar, background and quote mark. Defaults to `accent`. */\n color?: ThemeColor\n /** Quote text size. Defaults to `md`. */\n size?: BlockquoteSize\n /** Optional attribution shown below the quote (e.g. an author or source), prefixed with an em dash. */\n cite?: ReactNode\n /** Show the decorative leading quote mark. Defaults to `false`. */\n quoteMark?: boolean\n /** The quoted content. */\n children?: ReactNode\n}\n\n/**\n * A styled quotation block. Renders a semantic `<blockquote>` with a tinted left accent bar, an\n * optional decorative quote mark, and an optional `cite` attribution line. Tinted by `color` via the\n * shared `--tz-btn-rgb` pattern; `variant` toggles a soft tinted background (`filled`) vs. a bare bar\n * (`plain`). The attribution renders in a `<footer>` with a semantic `<cite>`.\n */\nexport const Blockquote = forwardRef<HTMLQuoteElement, BlockquoteProps>(function Blockquote(\n {\n variant = 'filled',\n color = 'accent',\n size = 'md',\n cite,\n quoteMark = false,\n className,\n style,\n children,\n ...props\n },\n ref,\n) {\n return (\n <blockquote\n ref={ref}\n className={clsx(styles.blockquote, styles[variant], styles[size], className)}\n style={{ '--tz-btn-rgb': `var(--tz-color-${color}-rgb)`, ...style } as CSSProperties}\n {...props}\n >\n {quoteMark && (\n <span className={styles.mark} aria-hidden>\n “\n </span>\n )}\n <div className={styles.body}>\n <div className={styles.content}>{children}</div>\n {cite != null && (\n <footer className={styles.cite}>\n <cite>{cite}</cite>\n </footer>\n )}\n </div>\n </blockquote>\n )\n})\n"],"names":["Blockquote","forwardRef","variant","color","size","cite","quoteMark","className","style","children","props","ref","jsxs","clsx","styles","jsx"],"mappings":";;;;;;;;;;;;;;GAqCaA,IAAaC,EAA8C,SACtE;AAAA,EACE,SAAAC,IAAU;AAAA,EACV,OAAAC,IAAQ;AAAA,EACR,MAAAC,IAAO;AAAA,EACP,MAAAC;AAAA,EACA,WAAAC,IAAY;AAAA,EACZ,WAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,GAAGC;AACL,GACAC,GACA;AACA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,WAAWE,EAAKC,EAAO,YAAYA,EAAOZ,CAAO,GAAGY,EAAOV,CAAI,GAAGG,CAAS;AAAA,MAC3E,OAAO,EAAE,gBAAgB,kBAAkBJ,CAAK,SAAS,GAAGK,EAAA;AAAA,MAC3D,GAAGE;AAAA,MAEH,UAAA;AAAA,QAAAJ,uBACE,QAAA,EAAK,WAAWQ,EAAO,MAAM,eAAW,IAAC,UAAA,IAAA,CAE1C;AAAA,QAEF,gBAAAF,EAAC,OAAA,EAAI,WAAWE,EAAO,MACrB,UAAA;AAAA,UAAA,gBAAAC,EAAC,OAAA,EAAI,WAAWD,EAAO,SAAU,UAAAL,GAAS;AAAA,UACzCJ,KAAQ,QACP,gBAAAU,EAAC,UAAA,EAAO,WAAWD,EAAO,MACxB,UAAA,gBAAAC,EAAC,QAAA,EAAM,UAAAV,EAAA,CAAK,EAAA,CACd;AAAA,QAAA,EAAA,CAEJ;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAGN,CAAC;"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const t=require("react/jsx-runtime"),m=require("react"),u=require("clsx"),b="_blockquote_11r93_3",k="_filled_11r93_16",q="_plain_11r93_19",f="_sm_11r93_24",x="_md_11r93_27",j="_lg_11r93_30",h="_mark_11r93_35",y="_body_11r93_45",g="_content_11r93_54",N="_cite_11r93_59",c={blockquote:b,filled:k,plain:q,sm:f,md:x,lg:j,mark:h,body:y,content:g,cite:N},p=m.forwardRef(function({variant:s="filled",color:o="accent",size:l="md",cite:e,quoteMark:n=!1,className:r,style:_,children:i,...a},d){return t.jsxs("blockquote",{ref:d,className:u.clsx(c.blockquote,c[s],c[l],r),style:{"--tz-btn-rgb":`var(--tz-color-${o}-rgb)`,..._},...a,children:[n&&t.jsx("span",{className:c.mark,"aria-hidden":!0,children:"“"}),t.jsxs("div",{className:c.body,children:[t.jsx("div",{className:c.content,children:i}),e!=null&&t.jsx("footer",{className:c.cite,children:t.jsx("cite",{children:e})})]})]})});exports.Blockquote=p;
|
|
2
|
+
//# sourceMappingURL=Blockquote-CxHwOecc.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Blockquote-CxHwOecc.cjs","sources":["../src/components/Blockquote/Blockquote.tsx"],"sourcesContent":["import {\n forwardRef,\n type BlockquoteHTMLAttributes,\n type CSSProperties,\n type ReactNode,\n} from 'react'\nimport { clsx } from 'clsx'\nimport type { ThemeColor } from '../../theme'\nimport styles from './Blockquote.module.css'\n\nexport type BlockquoteVariant = 'filled' | 'plain'\nexport type BlockquoteSize = 'sm' | 'md' | 'lg'\n\nexport interface BlockquoteProps extends Omit<\n BlockquoteHTMLAttributes<HTMLQuoteElement>,\n 'color' | 'cite'\n> {\n /** Tint style — `filled` (soft tinted background + accent bar) or `plain` (accent bar only). Defaults to `filled`. */\n variant?: BlockquoteVariant\n /** Theme palette token that tints the accent bar, background and quote mark. Defaults to `accent`. */\n color?: ThemeColor\n /** Quote text size. Defaults to `md`. */\n size?: BlockquoteSize\n /** Optional attribution shown below the quote (e.g. an author or source), prefixed with an em dash. */\n cite?: ReactNode\n /** Show the decorative leading quote mark. Defaults to `false`. */\n quoteMark?: boolean\n /** The quoted content. */\n children?: ReactNode\n}\n\n/**\n * A styled quotation block. Renders a semantic `<blockquote>` with a tinted left accent bar, an\n * optional decorative quote mark, and an optional `cite` attribution line. Tinted by `color` via the\n * shared `--tz-btn-rgb` pattern; `variant` toggles a soft tinted background (`filled`) vs. a bare bar\n * (`plain`). The attribution renders in a `<footer>` with a semantic `<cite>`.\n */\nexport const Blockquote = forwardRef<HTMLQuoteElement, BlockquoteProps>(function Blockquote(\n {\n variant = 'filled',\n color = 'accent',\n size = 'md',\n cite,\n quoteMark = false,\n className,\n style,\n children,\n ...props\n },\n ref,\n) {\n return (\n <blockquote\n ref={ref}\n className={clsx(styles.blockquote, styles[variant], styles[size], className)}\n style={{ '--tz-btn-rgb': `var(--tz-color-${color}-rgb)`, ...style } as CSSProperties}\n {...props}\n >\n {quoteMark && (\n <span className={styles.mark} aria-hidden>\n “\n </span>\n )}\n <div className={styles.body}>\n <div className={styles.content}>{children}</div>\n {cite != null && (\n <footer className={styles.cite}>\n <cite>{cite}</cite>\n </footer>\n )}\n </div>\n </blockquote>\n )\n})\n"],"names":["Blockquote","forwardRef","variant","color","size","cite","quoteMark","className","style","children","props","ref","jsxs","clsx","styles","jsx"],"mappings":"0WAqCaA,EAAaC,EAAAA,WAA8C,SACtE,CACE,QAAAC,EAAU,SACV,MAAAC,EAAQ,SACR,KAAAC,EAAO,KACP,KAAAC,EACA,UAAAC,EAAY,GACZ,UAAAC,EACA,MAAAC,EACA,SAAAC,EACA,GAAGC,CACL,EACAC,EACA,CACA,OACEC,EAAAA,KAAC,aAAA,CACC,IAAAD,EACA,UAAWE,EAAAA,KAAKC,EAAO,WAAYA,EAAOZ,CAAO,EAAGY,EAAOV,CAAI,EAAGG,CAAS,EAC3E,MAAO,CAAE,eAAgB,kBAAkBJ,CAAK,QAAS,GAAGK,CAAA,EAC3D,GAAGE,EAEH,SAAA,CAAAJ,SACE,OAAA,CAAK,UAAWQ,EAAO,KAAM,cAAW,GAAC,SAAA,GAAA,CAE1C,EAEFF,EAAAA,KAAC,MAAA,CAAI,UAAWE,EAAO,KACrB,SAAA,CAAAC,EAAAA,IAAC,MAAA,CAAI,UAAWD,EAAO,QAAU,SAAAL,EAAS,EACzCJ,GAAQ,MACPU,EAAAA,IAAC,SAAA,CAAO,UAAWD,EAAO,KACxB,SAAAC,EAAAA,IAAC,OAAA,CAAM,SAAAV,CAAA,CAAK,CAAA,CACd,CAAA,CAAA,CAEJ,CAAA,CAAA,CAAA,CAGN,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const d=require("react/jsx-runtime"),q=require("react"),i=require("clsx"),m="_code_19qlq_3",u="_soft_19qlq_18",f="_outlined_19qlq_21",a="_sm_19qlq_27",g="_md_19qlq_30",x="_lg_19qlq_33",o={code:m,soft:u,outlined:f,sm:a,md:g,lg:x},b=q.forwardRef(function({variant:s="soft",color:t="primary",size:e="md",className:c,style:n,children:l,...r},_){return d.jsx("code",{ref:_,className:i.clsx(o.code,o[s],o[e],c),style:{"--tz-btn-rgb":`var(--tz-color-${t}-rgb)`,...n},...r,children:l})});exports.Code=b;
|
|
2
|
+
//# sourceMappingURL=Code-CUdpWlY5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Code-CUdpWlY5.cjs","sources":["../src/components/Code/Code.tsx"],"sourcesContent":["import { forwardRef, type CSSProperties, type HTMLAttributes, type ReactNode } from 'react'\nimport { clsx } from 'clsx'\nimport type { ThemeColor } from '../../theme'\nimport styles from './Code.module.css'\n\nexport type CodeVariant = 'soft' | 'outlined'\nexport type CodeSize = 'sm' | 'md' | 'lg'\n\nexport interface CodeProps extends Omit<HTMLAttributes<HTMLElement>, 'color'> {\n /** Tint style — `soft` (subtle tinted background) or `outlined` (hairline border, transparent). Defaults to `soft`. */\n variant?: CodeVariant\n /** Theme palette token that tints the text and background. Defaults to `primary`. */\n color?: ThemeColor\n /** Text size. Defaults to `md`. */\n size?: CodeSize\n /** The inline code content. */\n children?: ReactNode\n}\n\n/**\n * An inline code snippet — a monospace `<code>` element with a soft rounded background, for calling out\n * commands, identifiers or short snippets within body text (e.g. `console.log(\"Hello\")`). Tinted by\n * `color` via the shared `--tz-btn-rgb` pattern; `variant` toggles a subtle tinted fill (`soft`) vs. a\n * hairline border (`outlined`). For a full multi-line, syntax-highlighted block use `CodeBlock` instead.\n */\nexport const Code = forwardRef<HTMLElement, CodeProps>(function Code(\n { variant = 'soft', color = 'primary', size = 'md', className, style, children, ...props },\n ref,\n) {\n return (\n <code\n ref={ref}\n className={clsx(styles.code, styles[variant], styles[size], className)}\n style={{ '--tz-btn-rgb': `var(--tz-color-${color}-rgb)`, ...style } as CSSProperties}\n {...props}\n >\n {children}\n </code>\n )\n})\n"],"names":["Code","forwardRef","variant","color","size","className","style","children","props","ref","jsx","clsx","styles"],"mappings":"kPAyBaA,EAAOC,EAAAA,WAAmC,SACrD,CAAE,QAAAC,EAAU,OAAQ,MAAAC,EAAQ,UAAW,KAAAC,EAAO,KAAM,UAAAC,EAAW,MAAAC,EAAO,SAAAC,EAAU,GAAGC,CAAA,EACnFC,EACA,CACA,OACEC,EAAAA,IAAC,OAAA,CACC,IAAAD,EACA,UAAWE,EAAAA,KAAKC,EAAO,KAAMA,EAAOV,CAAO,EAAGU,EAAOR,CAAI,EAAGC,CAAS,EACrE,MAAO,CAAE,eAAgB,kBAAkBF,CAAK,QAAS,GAAGG,CAAA,EAC3D,GAAGE,EAEH,SAAAD,CAAA,CAAA,CAGP,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as m } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as n } from "react";
|
|
3
|
+
import { clsx as q } from "clsx";
|
|
4
|
+
const f = "_code_19qlq_3", i = "_soft_19qlq_18", a = "_outlined_19qlq_21", g = "_sm_19qlq_27", p = "_md_19qlq_30", u = "_lg_19qlq_33", o = {
|
|
5
|
+
code: f,
|
|
6
|
+
soft: i,
|
|
7
|
+
outlined: a,
|
|
8
|
+
sm: g,
|
|
9
|
+
md: p,
|
|
10
|
+
lg: u
|
|
11
|
+
}, z = n(function({ variant: t = "soft", color: s = "primary", size: _ = "md", className: c, style: l, children: r, ...e }, d) {
|
|
12
|
+
return /* @__PURE__ */ m(
|
|
13
|
+
"code",
|
|
14
|
+
{
|
|
15
|
+
ref: d,
|
|
16
|
+
className: q(o.code, o[t], o[_], c),
|
|
17
|
+
style: { "--tz-btn-rgb": `var(--tz-color-${s}-rgb)`, ...l },
|
|
18
|
+
...e,
|
|
19
|
+
children: r
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
export {
|
|
24
|
+
z as C
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=Code-DaEpXHfF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Code-DaEpXHfF.js","sources":["../src/components/Code/Code.tsx"],"sourcesContent":["import { forwardRef, type CSSProperties, type HTMLAttributes, type ReactNode } from 'react'\nimport { clsx } from 'clsx'\nimport type { ThemeColor } from '../../theme'\nimport styles from './Code.module.css'\n\nexport type CodeVariant = 'soft' | 'outlined'\nexport type CodeSize = 'sm' | 'md' | 'lg'\n\nexport interface CodeProps extends Omit<HTMLAttributes<HTMLElement>, 'color'> {\n /** Tint style — `soft` (subtle tinted background) or `outlined` (hairline border, transparent). Defaults to `soft`. */\n variant?: CodeVariant\n /** Theme palette token that tints the text and background. Defaults to `primary`. */\n color?: ThemeColor\n /** Text size. Defaults to `md`. */\n size?: CodeSize\n /** The inline code content. */\n children?: ReactNode\n}\n\n/**\n * An inline code snippet — a monospace `<code>` element with a soft rounded background, for calling out\n * commands, identifiers or short snippets within body text (e.g. `console.log(\"Hello\")`). Tinted by\n * `color` via the shared `--tz-btn-rgb` pattern; `variant` toggles a subtle tinted fill (`soft`) vs. a\n * hairline border (`outlined`). For a full multi-line, syntax-highlighted block use `CodeBlock` instead.\n */\nexport const Code = forwardRef<HTMLElement, CodeProps>(function Code(\n { variant = 'soft', color = 'primary', size = 'md', className, style, children, ...props },\n ref,\n) {\n return (\n <code\n ref={ref}\n className={clsx(styles.code, styles[variant], styles[size], className)}\n style={{ '--tz-btn-rgb': `var(--tz-color-${color}-rgb)`, ...style } as CSSProperties}\n {...props}\n >\n {children}\n </code>\n )\n})\n"],"names":["Code","forwardRef","variant","color","size","className","style","children","props","ref","jsx","clsx","styles"],"mappings":";;;;;;;;;;GAyBaA,IAAOC,EAAmC,SACrD,EAAE,SAAAC,IAAU,QAAQ,OAAAC,IAAQ,WAAW,MAAAC,IAAO,MAAM,WAAAC,GAAW,OAAAC,GAAO,UAAAC,GAAU,GAAGC,EAAA,GACnFC,GACA;AACA,SACE,gBAAAC;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAD;AAAA,MACA,WAAWE,EAAKC,EAAO,MAAMA,EAAOV,CAAO,GAAGU,EAAOR,CAAI,GAAGC,CAAS;AAAA,MACrE,OAAO,EAAE,gBAAgB,kBAAkBF,CAAK,SAAS,GAAGG,EAAA;AAAA,MAC3D,GAAGE;AAAA,MAEH,UAAAD;AAAA,IAAA;AAAA,EAAA;AAGP,CAAC;"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { jsx as o, jsxs as _ } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as I, useState as h, useRef as S, useEffect as y } from "react";
|
|
3
|
+
import { clsx as w } from "clsx";
|
|
4
|
+
import { I as g } from "./IconButton-CBusrDa9.js";
|
|
5
|
+
import { I as z } from "./Icon-CophpkHo.js";
|
|
6
|
+
import { T as E } from "./Tooltip-Cb_FKxT9.js";
|
|
7
|
+
import { a as R } from "./ConfigProvider-2c94kUOk.js";
|
|
8
|
+
const j = "_root_floau_4", M = "_header_floau_15", D = "_title_floau_24", H = "_code_floau_34", L = "_plain_floau_50", O = "_wrap_floau_56", P = "_lineNumbers_floau_62", q = "_copy_floau_77", e = {
|
|
9
|
+
root: j,
|
|
10
|
+
header: M,
|
|
11
|
+
title: D,
|
|
12
|
+
code: H,
|
|
13
|
+
plain: L,
|
|
14
|
+
wrap: O,
|
|
15
|
+
lineNumbers: P,
|
|
16
|
+
copy: q
|
|
17
|
+
}, A = 1600, X = I(function({
|
|
18
|
+
code: l,
|
|
19
|
+
language: a = "tsx",
|
|
20
|
+
title: i,
|
|
21
|
+
copyable: b = !0,
|
|
22
|
+
showLineNumbers: N = !0,
|
|
23
|
+
maxHeight: d,
|
|
24
|
+
wrap: T = !1,
|
|
25
|
+
className: C,
|
|
26
|
+
style: k,
|
|
27
|
+
...v
|
|
28
|
+
}, B) {
|
|
29
|
+
const c = R(), [p, u] = h(null), [n, m] = h(!1), r = S(void 0);
|
|
30
|
+
y(() => {
|
|
31
|
+
let t = !1;
|
|
32
|
+
return import("shiki").then(
|
|
33
|
+
({ codeToHtml: s }) => s(l, {
|
|
34
|
+
lang: a,
|
|
35
|
+
// always VS Code's dark theme — the block stays dark in both app modes
|
|
36
|
+
theme: "dark-plus"
|
|
37
|
+
})
|
|
38
|
+
).then((s) => {
|
|
39
|
+
t || u(s);
|
|
40
|
+
}).catch(() => {
|
|
41
|
+
t || u(null);
|
|
42
|
+
}), () => {
|
|
43
|
+
t = !0;
|
|
44
|
+
};
|
|
45
|
+
}, [l, a]), y(() => () => window.clearTimeout(r.current), []);
|
|
46
|
+
const x = () => {
|
|
47
|
+
var t;
|
|
48
|
+
(t = navigator.clipboard) == null || t.writeText(l).then(() => {
|
|
49
|
+
m(!0), window.clearTimeout(r.current), r.current = window.setTimeout(() => m(!1), A);
|
|
50
|
+
}).catch(() => {
|
|
51
|
+
});
|
|
52
|
+
}, f = b && // the tooltip is hover/focus-driven; clicking flips `copied`, so its content updates to "Copied!"
|
|
53
|
+
// while it's still open (the pointer is on the button). `left` keeps it inside the block's
|
|
54
|
+
// `overflow: hidden` bounds.
|
|
55
|
+
/* @__PURE__ */ o(
|
|
56
|
+
E,
|
|
57
|
+
{
|
|
58
|
+
content: c(n ? "codeBlock.copied" : "codeBlock.copy"),
|
|
59
|
+
placement: "left",
|
|
60
|
+
style: { "--tz-btn-rgb": "255, 255, 255", "--tz-btn-on": "#1e1e1e" },
|
|
61
|
+
children: /* @__PURE__ */ o(
|
|
62
|
+
g,
|
|
63
|
+
{
|
|
64
|
+
variant: "text",
|
|
65
|
+
size: "sm",
|
|
66
|
+
className: e.copy,
|
|
67
|
+
style: { "--tz-btn-rgb": "255, 255, 255" },
|
|
68
|
+
"aria-label": c(n ? "codeBlock.copied" : "codeBlock.copy"),
|
|
69
|
+
onClick: x,
|
|
70
|
+
children: /* @__PURE__ */ o(z, { name: n ? "CopySuccess" : "Copy" })
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
return /* @__PURE__ */ _("div", { ref: B, className: w(e.root, C), style: k, ...v, children: [
|
|
76
|
+
(i != null || f) && /* @__PURE__ */ _("div", { className: e.header, children: [
|
|
77
|
+
/* @__PURE__ */ o("span", { className: e.title, children: i }),
|
|
78
|
+
f
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ o(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
className: w(e.code, T && e.wrap, N && e.lineNumbers),
|
|
84
|
+
style: d != null ? { maxHeight: d } : void 0,
|
|
85
|
+
children: p != null ? (
|
|
86
|
+
// shiki output is trusted markup — it escapes the source code itself
|
|
87
|
+
/* @__PURE__ */ o("div", { className: e.highlighted, dangerouslySetInnerHTML: { __html: p } })
|
|
88
|
+
) : /* @__PURE__ */ o("pre", { className: e.plain, children: /* @__PURE__ */ o("code", { children: l }) })
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] });
|
|
92
|
+
});
|
|
93
|
+
export {
|
|
94
|
+
X as C
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=CodeBlock-BWxQyKRo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlock-BWxQyKRo.js","sources":["../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { clsx } from 'clsx'\nimport { useT } from '../../theme'\nimport { IconButton } from '../IconButton'\nimport { Icon } from '../Icon'\nimport { Tooltip } from '../Tooltip'\nimport styles from './CodeBlock.module.css'\n\n// how long the copy button shows the \"copied\" tick before reverting\nconst COPIED_RESET_MS = 1600\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {\n /** The source code to render. */\n code: string\n /** Shiki language id (`'tsx'`, `'json'`, `'bash'`, `'css'`, …). Defaults to `'tsx'`. */\n language?: string\n /**\n * Optional label shown on the left of the header bar (e.g. a filename). The header bar itself always\n * renders (whenever `copyable`), so the copy button lives in it with or without a `title`.\n */\n title?: ReactNode\n /** Show a copy-to-clipboard button (in the header bar). Defaults to `true`. */\n copyable?: boolean\n /** Show line numbers. Defaults to `true`. */\n showLineNumbers?: boolean\n /** Cap the block height in px — taller code scrolls inside. */\n maxHeight?: number\n /** Soft-wrap long lines instead of scrolling horizontally. Defaults to `false`. */\n wrap?: boolean\n}\n\n/**\n * A syntax-highlighted code block with **VS Code colors** — powered by **Shiki** (an optional peer,\n * `npm i shiki`), the actual VS Code highlighting engine, using its **`dark-plus`** theme. The block\n * is **always dark** (in both app modes) — code reads best on a dark surface, and the deep background\n * anchors it regardless of the surrounding light/dark theme. Highlighting is **async and lazy**\n * (`import('shiki')` on first render); until it lands — or if `shiki` isn't installed / the language\n * is unknown — the block shows the plain code (on the same dark surface), so nothing breaks. A header\n * bar always renders (whenever `copyable`, default `true`) and holds the copy button — which flips to a\n * tick on click — with an optional `title` (e.g. a filename) on its left; `showLineNumbers` (default\n * `true`), `maxHeight` (scrolls inside) and `wrap` shape the body. The whole block\n * keeps VS Code's own dark colors (a deliberate literal-color exception, like the Modal scrim); only\n * the outer border/radius uses `--tz-*` tokens.\n */\nexport const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language = 'tsx',\n title,\n copyable = true,\n showLineNumbers = true,\n maxHeight,\n wrap = false,\n className,\n style,\n ...props\n },\n ref,\n) {\n const t = useT()\n const [html, setHtml] = useState<string | null>(null)\n const [copied, setCopied] = useState(false)\n const copyTimer = useRef<number | undefined>(undefined)\n\n // highlight asynchronously — shiki is an optional peer, loaded lazily on first use; a missing\n // package or an unknown language just leaves the plain (unhighlighted) fallback in place\n useEffect(() => {\n let cancelled = false\n import('shiki')\n .then(({ codeToHtml }) =>\n codeToHtml(code, {\n lang: language,\n // always VS Code's dark theme — the block stays dark in both app modes\n theme: 'dark-plus',\n }),\n )\n .then((out) => {\n if (!cancelled) setHtml(out)\n })\n .catch(() => {\n if (!cancelled) setHtml(null)\n })\n return () => {\n cancelled = true\n }\n }, [code, language])\n\n useEffect(() => () => window.clearTimeout(copyTimer.current), [])\n\n const handleCopy = () => {\n navigator.clipboard\n ?.writeText(code)\n .then(() => {\n setCopied(true)\n window.clearTimeout(copyTimer.current)\n copyTimer.current = window.setTimeout(() => setCopied(false), COPIED_RESET_MS)\n })\n .catch(() => {})\n }\n\n const copyButton = copyable && (\n // the tooltip is hover/focus-driven; clicking flips `copied`, so its content updates to \"Copied!\"\n // while it's still open (the pointer is on the button). `left` keeps it inside the block's\n // `overflow: hidden` bounds.\n <Tooltip\n content={copied ? t('codeBlock.copied') : t('codeBlock.copy')}\n placement=\"left\"\n // always a white pill with dark text over the (always-dark) block — set via `style` so it beats\n // Tooltip's own color-derived --tz-btn-rgb (which is spread before the consumer `style`)\n style={{ '--tz-btn-rgb': '255, 255, 255', '--tz-btn-on': '#1e1e1e' } as CSSProperties}\n >\n <IconButton\n variant=\"text\"\n size=\"sm\"\n className={styles.copy}\n // force the button light over the dark surface — passed via `style` so it beats IconButton's\n // own inline `--tz-btn-rgb` (which is spread before the consumer `style`, so this wins)\n style={{ '--tz-btn-rgb': '255, 255, 255' } as CSSProperties}\n aria-label={copied ? t('codeBlock.copied') : t('codeBlock.copy')}\n onClick={handleCopy}\n >\n <Icon name={copied ? 'CopySuccess' : 'Copy'} />\n </IconButton>\n </Tooltip>\n )\n\n return (\n <div ref={ref} className={clsx(styles.root, className)} style={style} {...props}>\n {/* the header bar always renders whenever there's a title or a copy button — so the copy icon\n lives in the bar (top-right), never floating over the code */}\n {(title != null || copyButton) && (\n <div className={styles.header}>\n <span className={styles.title}>{title}</span>\n {copyButton}\n </div>\n )}\n <div\n className={clsx(styles.code, wrap && styles.wrap, showLineNumbers && styles.lineNumbers)}\n style={maxHeight != null ? ({ maxHeight } as CSSProperties) : undefined}\n >\n {html != null ? (\n // shiki output is trusted markup — it escapes the source code itself\n <div className={styles.highlighted} dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n <pre className={styles.plain}>\n <code>{code}</code>\n </pre>\n )}\n </div>\n </div>\n )\n})\n"],"names":["COPIED_RESET_MS","CodeBlock","forwardRef","code","language","title","copyable","showLineNumbers","maxHeight","wrap","className","style","props","ref","t","useT","html","setHtml","useState","copied","setCopied","copyTimer","useRef","useEffect","cancelled","codeToHtml","out","handleCopy","_a","copyButton","jsx","Tooltip","IconButton","styles","Icon","jsxs","clsx"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var v=Object.create;var y=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,g=Object.prototype.hasOwnProperty;var E=(t,e,s,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of I(e))!g.call(t,n)&&n!==s&&y(t,n,{get:()=>e[n],enumerable:!(i=q(e,n))||i.enumerable});return t};var z=(t,e,s)=>(s=t!=null?v(S(t)):{},E(e||!t||!t.__esModule?y(s,"default",{value:t,enumerable:!0}):s,t));const o=require("react/jsx-runtime"),r=require("react"),w=require("clsx"),R=require("./IconButton-D6TenQZ_.cjs"),M=require("./Icon-CXTl_QAP.cjs"),P=require("./Tooltip-Ao7az09p.cjs"),D=require("./ConfigProvider-ByOFXVyP.cjs"),H="_root_floau_4",L="_header_floau_15",O="_title_floau_24",A="_code_floau_34",F="_plain_floau_50",G="_wrap_floau_56",J="_lineNumbers_floau_62",K="_copy_floau_77",c={root:H,header:L,title:O,code:A,plain:F,wrap:G,lineNumbers:J,copy:K},Q=1600,U=r.forwardRef(function({code:e,language:s="tsx",title:i,copyable:n=!0,showLineNumbers:b=!0,maxHeight:_,wrap:j=!1,className:N,style:B,...C},T){const a=D.useT(),[f,h]=r.useState(null),[u,m]=r.useState(!1),d=r.useRef(void 0);r.useEffect(()=>{let l=!1;return import("shiki").then(({codeToHtml:p})=>p(e,{lang:s,theme:"dark-plus"})).then(p=>{l||h(p)}).catch(()=>{l||h(null)}),()=>{l=!0}},[e,s]),r.useEffect(()=>()=>window.clearTimeout(d.current),[]);const k=()=>{var l;(l=navigator.clipboard)==null||l.writeText(e).then(()=>{m(!0),window.clearTimeout(d.current),d.current=window.setTimeout(()=>m(!1),Q)}).catch(()=>{})},x=n&&o.jsx(P.Tooltip,{content:a(u?"codeBlock.copied":"codeBlock.copy"),placement:"left",style:{"--tz-btn-rgb":"255, 255, 255","--tz-btn-on":"#1e1e1e"},children:o.jsx(R.IconButton,{variant:"text",size:"sm",className:c.copy,style:{"--tz-btn-rgb":"255, 255, 255"},"aria-label":a(u?"codeBlock.copied":"codeBlock.copy"),onClick:k,children:o.jsx(M.Icon,{name:u?"CopySuccess":"Copy"})})});return o.jsxs("div",{ref:T,className:w.clsx(c.root,N),style:B,...C,children:[(i!=null||x)&&o.jsxs("div",{className:c.header,children:[o.jsx("span",{className:c.title,children:i}),x]}),o.jsx("div",{className:w.clsx(c.code,j&&c.wrap,b&&c.lineNumbers),style:_!=null?{maxHeight:_}:void 0,children:f!=null?o.jsx("div",{className:c.highlighted,dangerouslySetInnerHTML:{__html:f}}):o.jsx("pre",{className:c.plain,children:o.jsx("code",{children:e})})})]})});exports.CodeBlock=U;
|
|
2
|
+
//# sourceMappingURL=CodeBlock-CgXOjKS-.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CodeBlock-CgXOjKS-.cjs","sources":["../src/components/CodeBlock/CodeBlock.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { clsx } from 'clsx'\nimport { useT } from '../../theme'\nimport { IconButton } from '../IconButton'\nimport { Icon } from '../Icon'\nimport { Tooltip } from '../Tooltip'\nimport styles from './CodeBlock.module.css'\n\n// how long the copy button shows the \"copied\" tick before reverting\nconst COPIED_RESET_MS = 1600\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> {\n /** The source code to render. */\n code: string\n /** Shiki language id (`'tsx'`, `'json'`, `'bash'`, `'css'`, …). Defaults to `'tsx'`. */\n language?: string\n /**\n * Optional label shown on the left of the header bar (e.g. a filename). The header bar itself always\n * renders (whenever `copyable`), so the copy button lives in it with or without a `title`.\n */\n title?: ReactNode\n /** Show a copy-to-clipboard button (in the header bar). Defaults to `true`. */\n copyable?: boolean\n /** Show line numbers. Defaults to `true`. */\n showLineNumbers?: boolean\n /** Cap the block height in px — taller code scrolls inside. */\n maxHeight?: number\n /** Soft-wrap long lines instead of scrolling horizontally. Defaults to `false`. */\n wrap?: boolean\n}\n\n/**\n * A syntax-highlighted code block with **VS Code colors** — powered by **Shiki** (an optional peer,\n * `npm i shiki`), the actual VS Code highlighting engine, using its **`dark-plus`** theme. The block\n * is **always dark** (in both app modes) — code reads best on a dark surface, and the deep background\n * anchors it regardless of the surrounding light/dark theme. Highlighting is **async and lazy**\n * (`import('shiki')` on first render); until it lands — or if `shiki` isn't installed / the language\n * is unknown — the block shows the plain code (on the same dark surface), so nothing breaks. A header\n * bar always renders (whenever `copyable`, default `true`) and holds the copy button — which flips to a\n * tick on click — with an optional `title` (e.g. a filename) on its left; `showLineNumbers` (default\n * `true`), `maxHeight` (scrolls inside) and `wrap` shape the body. The whole block\n * keeps VS Code's own dark colors (a deliberate literal-color exception, like the Modal scrim); only\n * the outer border/radius uses `--tz-*` tokens.\n */\nexport const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language = 'tsx',\n title,\n copyable = true,\n showLineNumbers = true,\n maxHeight,\n wrap = false,\n className,\n style,\n ...props\n },\n ref,\n) {\n const t = useT()\n const [html, setHtml] = useState<string | null>(null)\n const [copied, setCopied] = useState(false)\n const copyTimer = useRef<number | undefined>(undefined)\n\n // highlight asynchronously — shiki is an optional peer, loaded lazily on first use; a missing\n // package or an unknown language just leaves the plain (unhighlighted) fallback in place\n useEffect(() => {\n let cancelled = false\n import('shiki')\n .then(({ codeToHtml }) =>\n codeToHtml(code, {\n lang: language,\n // always VS Code's dark theme — the block stays dark in both app modes\n theme: 'dark-plus',\n }),\n )\n .then((out) => {\n if (!cancelled) setHtml(out)\n })\n .catch(() => {\n if (!cancelled) setHtml(null)\n })\n return () => {\n cancelled = true\n }\n }, [code, language])\n\n useEffect(() => () => window.clearTimeout(copyTimer.current), [])\n\n const handleCopy = () => {\n navigator.clipboard\n ?.writeText(code)\n .then(() => {\n setCopied(true)\n window.clearTimeout(copyTimer.current)\n copyTimer.current = window.setTimeout(() => setCopied(false), COPIED_RESET_MS)\n })\n .catch(() => {})\n }\n\n const copyButton = copyable && (\n // the tooltip is hover/focus-driven; clicking flips `copied`, so its content updates to \"Copied!\"\n // while it's still open (the pointer is on the button). `left` keeps it inside the block's\n // `overflow: hidden` bounds.\n <Tooltip\n content={copied ? t('codeBlock.copied') : t('codeBlock.copy')}\n placement=\"left\"\n // always a white pill with dark text over the (always-dark) block — set via `style` so it beats\n // Tooltip's own color-derived --tz-btn-rgb (which is spread before the consumer `style`)\n style={{ '--tz-btn-rgb': '255, 255, 255', '--tz-btn-on': '#1e1e1e' } as CSSProperties}\n >\n <IconButton\n variant=\"text\"\n size=\"sm\"\n className={styles.copy}\n // force the button light over the dark surface — passed via `style` so it beats IconButton's\n // own inline `--tz-btn-rgb` (which is spread before the consumer `style`, so this wins)\n style={{ '--tz-btn-rgb': '255, 255, 255' } as CSSProperties}\n aria-label={copied ? t('codeBlock.copied') : t('codeBlock.copy')}\n onClick={handleCopy}\n >\n <Icon name={copied ? 'CopySuccess' : 'Copy'} />\n </IconButton>\n </Tooltip>\n )\n\n return (\n <div ref={ref} className={clsx(styles.root, className)} style={style} {...props}>\n {/* the header bar always renders whenever there's a title or a copy button — so the copy icon\n lives in the bar (top-right), never floating over the code */}\n {(title != null || copyButton) && (\n <div className={styles.header}>\n <span className={styles.title}>{title}</span>\n {copyButton}\n </div>\n )}\n <div\n className={clsx(styles.code, wrap && styles.wrap, showLineNumbers && styles.lineNumbers)}\n style={maxHeight != null ? ({ maxHeight } as CSSProperties) : undefined}\n >\n {html != null ? (\n // shiki output is trusted markup — it escapes the source code itself\n <div className={styles.highlighted} dangerouslySetInnerHTML={{ __html: html }} />\n ) : (\n <pre className={styles.plain}>\n <code>{code}</code>\n </pre>\n )}\n </div>\n </div>\n )\n})\n"],"names":["COPIED_RESET_MS","CodeBlock","forwardRef","code","language","title","copyable","showLineNumbers","maxHeight","wrap","className","style","props","ref","t","useT","html","setHtml","useState","copied","setCopied","copyTimer","useRef","useEffect","cancelled","codeToHtml","out","handleCopy","_a","copyButton","jsx","Tooltip","IconButton","styles","Icon","jsxs","clsx"],"mappings":"k6BAiBMA,EAAkB,KAmCXC,EAAYC,EAAAA,WAA2C,SAClE,CACE,KAAAC,EACA,SAAAC,EAAW,MACX,MAAAC,EACA,SAAAC,EAAW,GACX,gBAAAC,EAAkB,GAClB,UAAAC,EACA,KAAAC,EAAO,GACP,UAAAC,EACA,MAAAC,EACA,GAAGC,CACL,EACAC,EACA,CACA,MAAMC,EAAIC,EAAAA,KAAA,EACJ,CAACC,EAAMC,CAAO,EAAIC,EAAAA,SAAwB,IAAI,EAC9C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAS,EAAK,EACpCG,EAAYC,EAAAA,OAA2B,MAAS,EAItDC,EAAAA,UAAU,IAAM,CACd,IAAIC,EAAY,GAChB,cAAO,OAAO,EACX,KAAK,CAAC,CAAE,WAAAC,CAAA,IACPA,EAAWtB,EAAM,CACf,KAAMC,EAEN,MAAO,WAAA,CACR,CAAA,EAEF,KAAMsB,GAAQ,CACRF,GAAWP,EAAQS,CAAG,CAC7B,CAAC,EACA,MAAM,IAAM,CACNF,GAAWP,EAAQ,IAAI,CAC9B,CAAC,EACI,IAAM,CACXO,EAAY,EACd,CACF,EAAG,CAACrB,EAAMC,CAAQ,CAAC,EAEnBmB,EAAAA,UAAU,IAAM,IAAM,OAAO,aAAaF,EAAU,OAAO,EAAG,EAAE,EAEhE,MAAMM,EAAa,IAAM,QACvBC,EAAA,UAAU,YAAV,MAAAA,EACI,UAAUzB,GACX,KAAK,IAAM,CACViB,EAAU,EAAI,EACd,OAAO,aAAaC,EAAU,OAAO,EACrCA,EAAU,QAAU,OAAO,WAAW,IAAMD,EAAU,EAAK,EAAGpB,CAAe,CAC/E,GACC,MAAM,IAAM,CAAC,EAClB,EAEM6B,EAAavB,GAIjBwB,EAAAA,IAACC,EAAAA,QAAA,CACC,QAAkBjB,EAATK,EAAW,mBAAwB,gBAAN,EACtC,UAAU,OAGV,MAAO,CAAE,eAAgB,gBAAiB,cAAe,SAAA,EAEzD,SAAAW,EAAAA,IAACE,EAAAA,WAAA,CACC,QAAQ,OACR,KAAK,KACL,UAAWC,EAAO,KAGlB,MAAO,CAAE,eAAgB,eAAA,EACzB,aAAqBnB,EAATK,EAAW,mBAAwB,gBAAN,EACzC,QAASQ,EAET,SAAAG,EAAAA,IAACI,OAAA,CAAK,KAAMf,EAAS,cAAgB,MAAA,CAAQ,CAAA,CAAA,CAC/C,CAAA,EAIJ,OACEgB,EAAAA,KAAC,MAAA,CAAI,IAAAtB,EAAU,UAAWuB,EAAAA,KAAKH,EAAO,KAAMvB,CAAS,EAAG,MAAAC,EAAe,GAAGC,EAGtE,SAAA,EAAAP,GAAS,MAAQwB,IACjBM,EAAAA,KAAC,MAAA,CAAI,UAAWF,EAAO,OACrB,SAAA,CAAAH,EAAAA,IAAC,OAAA,CAAK,UAAWG,EAAO,MAAQ,SAAA5B,EAAM,EACrCwB,CAAA,EACH,EAEFC,EAAAA,IAAC,MAAA,CACC,UAAWM,EAAAA,KAAKH,EAAO,KAAMxB,GAAQwB,EAAO,KAAM1B,GAAmB0B,EAAO,WAAW,EACvF,MAAOzB,GAAa,KAAQ,CAAE,UAAAA,GAAgC,OAE7D,SAAAQ,GAAQ,KAEPc,MAAC,OAAI,UAAWG,EAAO,YAAa,wBAAyB,CAAE,OAAQjB,EAAK,CAAG,EAE/Ec,EAAAA,IAAC,OAAI,UAAWG,EAAO,MACrB,SAAAH,EAAAA,IAAC,OAAA,CAAM,WAAK,CAAA,CACd,CAAA,CAAA,CAEJ,EACF,CAEJ,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { jsx as y } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef as D, useMemo as E, Children as G, useState as R, useRef as v, useCallback as b, useLayoutEffect as O, useEffect as _ } from "react";
|
|
3
|
+
import { s as C } from "./Flex-Bj3mMF5A.js";
|
|
4
|
+
const j = 200, q = (u, r) => u.length === r.length && u.every((l, m) => l === r[m]), W = D(function({ columns: r = 3, minColumnWidth: l, gap: m = "md", padding: I, className: w, style: A, children: g, ...H }, i) {
|
|
5
|
+
const h = E(() => G.toArray(g), [g]), n = h.length, [c, T] = R(() => Math.max(1, r)), [x, S] = R([]), p = v(null), d = v([]), k = b(
|
|
6
|
+
(e) => {
|
|
7
|
+
p.current = e, typeof i == "function" ? i(e) : i && (i.current = e);
|
|
8
|
+
},
|
|
9
|
+
[i]
|
|
10
|
+
), f = b(() => {
|
|
11
|
+
const e = p.current;
|
|
12
|
+
if (!e) return;
|
|
13
|
+
let o = Math.max(1, r);
|
|
14
|
+
if (l != null) {
|
|
15
|
+
const t = parseFloat(getComputedStyle(e).columnGap) || 0, a = e.clientWidth;
|
|
16
|
+
o = Math.max(1, Math.floor((a + t) / (l + t)));
|
|
17
|
+
}
|
|
18
|
+
T((t) => t === o ? t : o);
|
|
19
|
+
const s = d.current.slice(0, n).map((t) => t ? t.offsetHeight : 0);
|
|
20
|
+
S((t) => q(t, s) ? t : s);
|
|
21
|
+
}, [r, l, n]);
|
|
22
|
+
O(f, [f, h, c]), _(() => {
|
|
23
|
+
const e = p.current;
|
|
24
|
+
if (!e || typeof ResizeObserver > "u") return;
|
|
25
|
+
const o = new ResizeObserver(() => f());
|
|
26
|
+
return o.observe(e), d.current.slice(0, n).forEach((s) => s && o.observe(s)), () => o.disconnect();
|
|
27
|
+
}, [f, n, c]);
|
|
28
|
+
const z = E(() => {
|
|
29
|
+
const e = Array.from({ length: c }, () => []), o = new Array(c).fill(0);
|
|
30
|
+
for (let s = 0; s < n; s++) {
|
|
31
|
+
let t = 0;
|
|
32
|
+
for (let a = 1; a < c; a++) o[a] < o[t] && (t = a);
|
|
33
|
+
e[t].push(s), o[t] += x[s] || j;
|
|
34
|
+
}
|
|
35
|
+
return e;
|
|
36
|
+
}, [c, n, x]), M = C(m);
|
|
37
|
+
return /* @__PURE__ */ y(
|
|
38
|
+
"div",
|
|
39
|
+
{
|
|
40
|
+
ref: k,
|
|
41
|
+
className: w,
|
|
42
|
+
style: {
|
|
43
|
+
display: "flex",
|
|
44
|
+
alignItems: "flex-start",
|
|
45
|
+
gap: M,
|
|
46
|
+
padding: C(I),
|
|
47
|
+
...A
|
|
48
|
+
},
|
|
49
|
+
...H,
|
|
50
|
+
children: z.map((e, o) => /* @__PURE__ */ y(
|
|
51
|
+
"div",
|
|
52
|
+
{
|
|
53
|
+
style: {
|
|
54
|
+
display: "flex",
|
|
55
|
+
flexDirection: "column",
|
|
56
|
+
gap: M,
|
|
57
|
+
flex: "1 1 0",
|
|
58
|
+
minWidth: 0
|
|
59
|
+
},
|
|
60
|
+
children: e.map((s) => /* @__PURE__ */ y(
|
|
61
|
+
"div",
|
|
62
|
+
{
|
|
63
|
+
ref: (t) => {
|
|
64
|
+
d.current[s] = t;
|
|
65
|
+
},
|
|
66
|
+
style: { minWidth: 0 },
|
|
67
|
+
children: h[s]
|
|
68
|
+
},
|
|
69
|
+
s
|
|
70
|
+
))
|
|
71
|
+
},
|
|
72
|
+
o
|
|
73
|
+
))
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
export {
|
|
78
|
+
W as M
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=Masonry--bT8SICO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry--bT8SICO.js","sources":["../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n Children,\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { space, type Spacing } from '../Flex/Flex'\n\n// nominal height for a not-yet-measured item, so the first (pre-measure) pass spreads items across\n// columns round-robin instead of dumping them all into the first one; corrected on the layout pass.\nconst ESTIMATED_ITEM_HEIGHT = 200\n\nexport interface MasonryProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed number of columns. Defaults to `3`. Ignored when `minColumnWidth` is set. */\n columns?: number\n /** Responsive: fit as many equal columns as this min pixel width allows (overrides `columns`). */\n minColumnWidth?: number\n /** Gap between items, horizontally and vertically (token key, px number, or CSS value). Defaults to `md`. */\n gap?: Spacing\n /** Padding around the grid (token key, px number, or CSS value). */\n padding?: Spacing\n /** The items to lay out. */\n children?: ReactNode\n}\n\nconst arraysEqual = (a: number[], b: number[]) =>\n a.length === b.length && a.every((v, i) => v === b[i])\n\n/**\n * A masonry layout — packs items of varying heights into balanced columns, always adding the next item\n * to the **shortest** column (Pinterest-style), so columns stay level rather than following a fixed\n * grid. Item heights are measured in the DOM (a `ResizeObserver` re-balances on content/size changes),\n * so it works with any children. Use a fixed `columns` count or a responsive `minColumnWidth`; `gap`\n * spaces items on both axes. Inline-styled like the other layout primitives (`Flex` / `Grid`); consumer\n * `style` merges last. Reading order flows left-to-right across the columns.\n */\nexport const Masonry = forwardRef<HTMLDivElement, MasonryProps>(function Masonry(\n { columns = 3, minColumnWidth, gap = 'md', padding, className, style, children, ...props },\n ref,\n) {\n const items = useMemo(() => Children.toArray(children), [children])\n const count = items.length\n\n const [colCount, setColCount] = useState(() => Math.max(1, columns))\n const [heights, setHeights] = useState<number[]>([])\n\n const rootRef = useRef<HTMLDivElement | null>(null)\n const itemRefs = useRef<Array<HTMLDivElement | null>>([])\n\n // merge the internal measuring ref with the forwarded ref\n const setRootRef = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // measure the container width → column count, and each item's height → the balance\n const measure = useCallback(() => {\n const root = rootRef.current\n if (!root) return\n\n let nextCols = Math.max(1, columns)\n if (minColumnWidth != null) {\n const g = parseFloat(getComputedStyle(root).columnGap) || 0\n const width = root.clientWidth\n nextCols = Math.max(1, Math.floor((width + g) / (minColumnWidth + g)))\n }\n setColCount((prev) => (prev === nextCols ? prev : nextCols))\n\n const next = itemRefs.current.slice(0, count).map((el) => (el ? el.offsetHeight : 0))\n setHeights((prev) => (arraysEqual(prev, next) ? prev : next))\n }, [columns, minColumnWidth, count])\n\n // measure before paint (mount + whenever the item set or column count changes)\n useLayoutEffect(measure, [measure, items, colCount])\n\n // re-balance on container resize and on any item resizing (image loads, dynamic content, …)\n useEffect(() => {\n const root = rootRef.current\n if (!root || typeof ResizeObserver === 'undefined') return\n const ro = new ResizeObserver(() => measure())\n ro.observe(root)\n itemRefs.current.slice(0, count).forEach((el) => el && ro.observe(el))\n return () => ro.disconnect()\n }, [measure, count, colCount])\n\n // greedily assign each item (in order) to the currently shortest column\n const columnsItems = useMemo(() => {\n const buckets: number[][] = Array.from({ length: colCount }, () => [])\n const acc = new Array(colCount).fill(0)\n for (let i = 0; i < count; i++) {\n let min = 0\n for (let c = 1; c < colCount; c++) if (acc[c] < acc[min]) min = c\n buckets[min].push(i)\n acc[min] += heights[i] || ESTIMATED_ITEM_HEIGHT\n }\n return buckets\n }, [colCount, count, heights])\n\n const gapValue = space(gap)\n\n return (\n <div\n ref={setRootRef}\n className={className}\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: gapValue,\n padding: space(padding),\n ...style,\n }}\n {...props}\n >\n {columnsItems.map((indices, c) => (\n <div\n key={c}\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: gapValue,\n flex: '1 1 0',\n minWidth: 0,\n }}\n >\n {indices.map((i) => (\n <div\n key={i}\n ref={(el) => {\n itemRefs.current[i] = el\n }}\n style={{ minWidth: 0 } as CSSProperties}\n >\n {items[i]}\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n})\n"],"names":["ESTIMATED_ITEM_HEIGHT","arraysEqual","a","b","v","i","Masonry","forwardRef","columns","minColumnWidth","gap","padding","className","style","children","props","ref","items","useMemo","Children","count","colCount","setColCount","useState","heights","setHeights","rootRef","useRef","itemRefs","setRootRef","useCallback","node","measure","root","nextCols","g","width","prev","next","el","useLayoutEffect","useEffect","ro","columnsItems","buckets","acc","min","c","gapValue","space","jsx","indices"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";const x=require("react/jsx-runtime"),r=require("react"),E=require("./Flex-CqL3Ghda.cjs"),H=200,S=(h,o)=>h.length===o.length&&h.every((u,m)=>u===o[m]),T=r.forwardRef(function({columns:o=3,minColumnWidth:u,gap:m="md",padding:b,className:v,style:C,children:g,...I},i){const y=r.useMemo(()=>r.Children.toArray(g),[g]),c=y.length,[a,j]=r.useState(()=>Math.max(1,o)),[M,q]=r.useState([]),d=r.useRef(null),p=r.useRef([]),w=r.useCallback(t=>{d.current=t,typeof i=="function"?i(t):i&&(i.current=t)},[i]),f=r.useCallback(()=>{const t=d.current;if(!t)return;let n=Math.max(1,o);if(u!=null){const e=parseFloat(getComputedStyle(t).columnGap)||0,l=t.clientWidth;n=Math.max(1,Math.floor((l+e)/(u+e)))}j(e=>e===n?e:n);const s=p.current.slice(0,c).map(e=>e?e.offsetHeight:0);q(e=>S(e,s)?e:s)},[o,u,c]);r.useLayoutEffect(f,[f,y,a]),r.useEffect(()=>{const t=d.current;if(!t||typeof ResizeObserver>"u")return;const n=new ResizeObserver(()=>f());return n.observe(t),p.current.slice(0,c).forEach(s=>s&&n.observe(s)),()=>n.disconnect()},[f,c,a]);const A=r.useMemo(()=>{const t=Array.from({length:a},()=>[]),n=new Array(a).fill(0);for(let s=0;s<c;s++){let e=0;for(let l=1;l<a;l++)n[l]<n[e]&&(e=l);t[e].push(s),n[e]+=M[s]||H}return t},[a,c,M]),R=E.space(m);return x.jsx("div",{ref:w,className:v,style:{display:"flex",alignItems:"flex-start",gap:R,padding:E.space(b),...C},...I,children:A.map((t,n)=>x.jsx("div",{style:{display:"flex",flexDirection:"column",gap:R,flex:"1 1 0",minWidth:0},children:t.map(s=>x.jsx("div",{ref:e=>{p.current[s]=e},style:{minWidth:0},children:y[s]},s))},n))})});exports.Masonry=T;
|
|
2
|
+
//# sourceMappingURL=Masonry-DlD-rIQX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Masonry-DlD-rIQX.cjs","sources":["../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n Children,\n forwardRef,\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { space, type Spacing } from '../Flex/Flex'\n\n// nominal height for a not-yet-measured item, so the first (pre-measure) pass spreads items across\n// columns round-robin instead of dumping them all into the first one; corrected on the layout pass.\nconst ESTIMATED_ITEM_HEIGHT = 200\n\nexport interface MasonryProps extends HTMLAttributes<HTMLDivElement> {\n /** Fixed number of columns. Defaults to `3`. Ignored when `minColumnWidth` is set. */\n columns?: number\n /** Responsive: fit as many equal columns as this min pixel width allows (overrides `columns`). */\n minColumnWidth?: number\n /** Gap between items, horizontally and vertically (token key, px number, or CSS value). Defaults to `md`. */\n gap?: Spacing\n /** Padding around the grid (token key, px number, or CSS value). */\n padding?: Spacing\n /** The items to lay out. */\n children?: ReactNode\n}\n\nconst arraysEqual = (a: number[], b: number[]) =>\n a.length === b.length && a.every((v, i) => v === b[i])\n\n/**\n * A masonry layout — packs items of varying heights into balanced columns, always adding the next item\n * to the **shortest** column (Pinterest-style), so columns stay level rather than following a fixed\n * grid. Item heights are measured in the DOM (a `ResizeObserver` re-balances on content/size changes),\n * so it works with any children. Use a fixed `columns` count or a responsive `minColumnWidth`; `gap`\n * spaces items on both axes. Inline-styled like the other layout primitives (`Flex` / `Grid`); consumer\n * `style` merges last. Reading order flows left-to-right across the columns.\n */\nexport const Masonry = forwardRef<HTMLDivElement, MasonryProps>(function Masonry(\n { columns = 3, minColumnWidth, gap = 'md', padding, className, style, children, ...props },\n ref,\n) {\n const items = useMemo(() => Children.toArray(children), [children])\n const count = items.length\n\n const [colCount, setColCount] = useState(() => Math.max(1, columns))\n const [heights, setHeights] = useState<number[]>([])\n\n const rootRef = useRef<HTMLDivElement | null>(null)\n const itemRefs = useRef<Array<HTMLDivElement | null>>([])\n\n // merge the internal measuring ref with the forwarded ref\n const setRootRef = useCallback(\n (node: HTMLDivElement | null) => {\n rootRef.current = node\n if (typeof ref === 'function') ref(node)\n else if (ref) ref.current = node\n },\n [ref],\n )\n\n // measure the container width → column count, and each item's height → the balance\n const measure = useCallback(() => {\n const root = rootRef.current\n if (!root) return\n\n let nextCols = Math.max(1, columns)\n if (minColumnWidth != null) {\n const g = parseFloat(getComputedStyle(root).columnGap) || 0\n const width = root.clientWidth\n nextCols = Math.max(1, Math.floor((width + g) / (minColumnWidth + g)))\n }\n setColCount((prev) => (prev === nextCols ? prev : nextCols))\n\n const next = itemRefs.current.slice(0, count).map((el) => (el ? el.offsetHeight : 0))\n setHeights((prev) => (arraysEqual(prev, next) ? prev : next))\n }, [columns, minColumnWidth, count])\n\n // measure before paint (mount + whenever the item set or column count changes)\n useLayoutEffect(measure, [measure, items, colCount])\n\n // re-balance on container resize and on any item resizing (image loads, dynamic content, …)\n useEffect(() => {\n const root = rootRef.current\n if (!root || typeof ResizeObserver === 'undefined') return\n const ro = new ResizeObserver(() => measure())\n ro.observe(root)\n itemRefs.current.slice(0, count).forEach((el) => el && ro.observe(el))\n return () => ro.disconnect()\n }, [measure, count, colCount])\n\n // greedily assign each item (in order) to the currently shortest column\n const columnsItems = useMemo(() => {\n const buckets: number[][] = Array.from({ length: colCount }, () => [])\n const acc = new Array(colCount).fill(0)\n for (let i = 0; i < count; i++) {\n let min = 0\n for (let c = 1; c < colCount; c++) if (acc[c] < acc[min]) min = c\n buckets[min].push(i)\n acc[min] += heights[i] || ESTIMATED_ITEM_HEIGHT\n }\n return buckets\n }, [colCount, count, heights])\n\n const gapValue = space(gap)\n\n return (\n <div\n ref={setRootRef}\n className={className}\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: gapValue,\n padding: space(padding),\n ...style,\n }}\n {...props}\n >\n {columnsItems.map((indices, c) => (\n <div\n key={c}\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: gapValue,\n flex: '1 1 0',\n minWidth: 0,\n }}\n >\n {indices.map((i) => (\n <div\n key={i}\n ref={(el) => {\n itemRefs.current[i] = el\n }}\n style={{ minWidth: 0 } as CSSProperties}\n >\n {items[i]}\n </div>\n ))}\n </div>\n ))}\n </div>\n )\n})\n"],"names":["ESTIMATED_ITEM_HEIGHT","arraysEqual","a","b","v","i","Masonry","forwardRef","columns","minColumnWidth","gap","padding","className","style","children","props","ref","items","useMemo","Children","count","colCount","setColCount","useState","heights","setHeights","rootRef","useRef","itemRefs","setRootRef","useCallback","node","measure","root","nextCols","g","width","prev","next","el","useLayoutEffect","useEffect","ro","columnsItems","buckets","acc","min","c","gapValue","space","jsx","indices"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BlockquoteHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import type { ThemeColor } from '../../theme';
|
|
3
|
+
export type BlockquoteVariant = 'filled' | 'plain';
|
|
4
|
+
export type BlockquoteSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface BlockquoteProps extends Omit<BlockquoteHTMLAttributes<HTMLQuoteElement>, 'color' | 'cite'> {
|
|
6
|
+
/** Tint style — `filled` (soft tinted background + accent bar) or `plain` (accent bar only). Defaults to `filled`. */
|
|
7
|
+
variant?: BlockquoteVariant;
|
|
8
|
+
/** Theme palette token that tints the accent bar, background and quote mark. Defaults to `accent`. */
|
|
9
|
+
color?: ThemeColor;
|
|
10
|
+
/** Quote text size. Defaults to `md`. */
|
|
11
|
+
size?: BlockquoteSize;
|
|
12
|
+
/** Optional attribution shown below the quote (e.g. an author or source), prefixed with an em dash. */
|
|
13
|
+
cite?: ReactNode;
|
|
14
|
+
/** Show the decorative leading quote mark. Defaults to `false`. */
|
|
15
|
+
quoteMark?: boolean;
|
|
16
|
+
/** The quoted content. */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A styled quotation block. Renders a semantic `<blockquote>` with a tinted left accent bar, an
|
|
21
|
+
* optional decorative quote mark, and an optional `cite` attribution line. Tinted by `color` via the
|
|
22
|
+
* shared `--tz-btn-rgb` pattern; `variant` toggles a soft tinted background (`filled`) vs. a bare bar
|
|
23
|
+
* (`plain`). The attribution renders in a `<footer>` with a semantic `<cite>`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const Blockquote: import("react").ForwardRefExoticComponent<BlockquoteProps & import("react").RefAttributes<HTMLQuoteElement>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type BlockquoteHTMLAttributes, type ReactNode } from 'react';
|
|
2
|
+
import type { ThemeColor } from '../../theme';
|
|
3
|
+
export type BlockquoteVariant = 'filled' | 'plain';
|
|
4
|
+
export type BlockquoteSize = 'sm' | 'md' | 'lg';
|
|
5
|
+
export interface BlockquoteProps extends Omit<BlockquoteHTMLAttributes<HTMLQuoteElement>, 'color' | 'cite'> {
|
|
6
|
+
/** Tint style — `filled` (soft tinted background + accent bar) or `plain` (accent bar only). Defaults to `filled`. */
|
|
7
|
+
variant?: BlockquoteVariant;
|
|
8
|
+
/** Theme palette token that tints the accent bar, background and quote mark. Defaults to `accent`. */
|
|
9
|
+
color?: ThemeColor;
|
|
10
|
+
/** Quote text size. Defaults to `md`. */
|
|
11
|
+
size?: BlockquoteSize;
|
|
12
|
+
/** Optional attribution shown below the quote (e.g. an author or source), prefixed with an em dash. */
|
|
13
|
+
cite?: ReactNode;
|
|
14
|
+
/** Show the decorative leading quote mark. Defaults to `false`. */
|
|
15
|
+
quoteMark?: boolean;
|
|
16
|
+
/** The quoted content. */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A styled quotation block. Renders a semantic `<blockquote>` with a tinted left accent bar, an
|
|
21
|
+
* optional decorative quote mark, and an optional `cite` attribution line. Tinted by `color` via the
|
|
22
|
+
* shared `--tz-btn-rgb` pattern; `variant` toggles a soft tinted background (`filled`) vs. a bare bar
|
|
23
|
+
* (`plain`). The attribution renders in a `<footer>` with a semantic `<cite>`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const Blockquote: import("react").ForwardRefExoticComponent<BlockquoteProps & import("react").RefAttributes<HTMLQuoteElement>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|