@chan.run/design 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # @chan.run/design
2
+
3
+ Shared Chakra UI v3 theme and brand-asset library for the chan.run ecosystem. **Single source of truth** for tokens, components, product icons, wordmarks, and favicons. Every site, app, and product UI imports from this package — never inline an SVG, never copy a PNG.
4
+
5
+ ## Install
6
+
7
+ The package is published to npm and pinned by every workspace consumer:
8
+
9
+ ```jsonc
10
+ // package.json
11
+ {
12
+ "dependencies": {
13
+ "@chakra-ui/react": "3.35.0",
14
+ "@chan.run/design": "^0.2.0",
15
+ "next-themes": "*",
16
+ "react": "19.2.4"
17
+ }
18
+ }
19
+ ```
20
+
21
+ `@chakra-ui/react`, `next-themes`, and `react` are peer deps — keep them aligned to the versions in this package's `peerDependencies` to avoid duplicate Chakra installs (which produce structural type errors when passing `system` between packages).
22
+
23
+ ## Theme + provider
24
+
25
+ ```tsx
26
+ import { ChakraProvider } from "@chakra-ui/react";
27
+ import { system } from "@chan.run/design";
28
+ import { ThemeProvider } from "next-themes";
29
+
30
+ <ChakraProvider value={system}>
31
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
32
+ {children}
33
+ </ThemeProvider>
34
+ </ChakraProvider>
35
+ ```
36
+
37
+ Tokens live in the `chan.*` namespace (`chan.bg`, `chan.text`, `chan.orange`, …) — see [`src/theme.ts`](./src/theme.ts) and `docs/design-system.md` for the full taxonomy.
38
+
39
+ ## Brand marks
40
+
41
+ Two flavors: the **chan.run umbrella brand** (`CHAN[•]RUN`, dot accent) and **per-product marks** (`SEA[M]`, `ENTROP[Y]`, …, last-letter accent). Rules locked per `docs/brand-marks.md`.
42
+
43
+ ### React components
44
+
45
+ ```tsx
46
+ import {
47
+ Wordmark, // CHAN[•]RUN — umbrella brand
48
+ SeamIcon, SeamWordmark,
49
+ EntropyIcon, EntropyWordmark,
50
+ RestunnelIcon, RestunnelWordmark,
51
+ EnsureIcon, EnsureWordmark,
52
+ SlidesIcon, SlidesWordmark,
53
+ PRODUCT_ICONS, // Record<name, ComponentType>
54
+ PRODUCT_WORDMARKS,
55
+ } from "@chan.run/design";
56
+
57
+ // Themed React component — switches with light/dark mode automatically
58
+ <HStack gap="3">
59
+ <SeamIcon />
60
+ <SeamWordmark fontSize="22px" />
61
+ </HStack>
62
+
63
+ // Loop products from the registry
64
+ {Object.entries(PRODUCT_ICONS).map(([name, Icon]) => <Icon key={name} />)}
65
+ ```
66
+
67
+ `<Wordmark pulseDot />` opacity-cycles the dot via the `chan-pulse` keyframe — used by `Splash` for loading states.
68
+
69
+ ### Raw SVG assets
70
+
71
+ When you need the SVG as an image (e.g. inside Chakra's `<Image>`):
72
+
73
+ ```tsx
74
+ import seamIconUrl from "@chan.run/design/svg/seam-icon-dark.svg?url";
75
+ import { Image } from "@chakra-ui/react";
76
+
77
+ <Image src={seamIconUrl} alt="" boxSize="44px" />
78
+ ```
79
+
80
+ Variants under `@chan.run/design/svg/`:
81
+
82
+ | Pattern | What |
83
+ | ------------------------------------------- | -------------------------------------------- |
84
+ | `chan-wordmark-{dark,light}.svg` | CHAN[•]RUN, dark or light text |
85
+ | `<name>-wordmark-{dark,light}.svg` | Product wordmark (text-as-paths) |
86
+ | `<name>-icon-{dark,light}.svg` | 176×176 squircle monogram |
87
+ | `<name>-favicon.svg` | 32×32 single-letter favicon source |
88
+
89
+ Where `<name>` ∈ `chan` (wordmark only) | `ensure` | `entropy` | `restunnel` | `seam` | `slides`. SVGs use **path-converted text** (no font dependency) and survive `pnpm build` (live in `assets/`, not `dist/`).
90
+
91
+ For theme-aware rendering, prefer the React component. The static `-dark`/`-light` SVGs are for surfaces where you can't use Chakra (Rust dashboards, native apps, README screenshots, etc.).
92
+
93
+ ## Favicons
94
+
95
+ Each product ships a full favicon bundle (16/32/48 PNG, ICO, Apple touch, Android Chrome, Windows mstile, manifest, head snippet). Three ways to consume:
96
+
97
+ ### 1. Head component (drop-in)
98
+
99
+ ```tsx
100
+ import { SeamFaviconHead } from "@chan.run/design";
101
+
102
+ <head>
103
+ <SeamFaviconHead />
104
+ {/* opt into PWA: <SeamFaviconHead manifest /> */}
105
+ </head>
106
+ ```
107
+
108
+ Per-product components: `ChanFaviconHead`, `EnsureFaviconHead`, `EntropyFaviconHead`, `RestunnelFaviconHead`, `SeamFaviconHead`, `SlidesFaviconHead`. Each emits the full set of `<link>`/`<meta>` tags with hashed asset URLs the bundler emits at build time.
109
+
110
+ ### 2. Asset URL imports (raw URLs for custom wiring)
111
+
112
+ ```tsx
113
+ import faviconIco from "@chan.run/design/favicons/seam/favicon.ico?url";
114
+ import appleTouchIcon from "@chan.run/design/favicons/seam/apple-touch-icon.png?url";
115
+ import manifestUrl from "@chan.run/design/favicons/seam/manifest.webmanifest?url";
116
+
117
+ <link rel="icon" href={faviconIco} />
118
+ ```
119
+
120
+ ### 3. Asset-URL maps (programmatic)
121
+
122
+ ```tsx
123
+ import { seamFaviconAssets } from "@chan.run/design";
124
+ seamFaviconAssets.ico; // bundler-emitted hashed URL
125
+ seamFaviconAssets.png16;
126
+ seamFaviconAssets.apple;
127
+ seamFaviconAssets.android192;
128
+ seamFaviconAssets.manifest;
129
+ ```
130
+
131
+ ### Browser extensions
132
+
133
+ Vite-style `?url` imports don't work inside `wxt.config.ts` (it's loaded by Node, not Vite). Use `createRequire` to get a filesystem path, then let `@wxt-dev/auto-icons` downscale to the manifest sizes:
134
+
135
+ ```ts
136
+ // wxt.config.ts
137
+ import { createRequire } from 'node:module';
138
+ import { defineConfig } from 'wxt';
139
+
140
+ const require = createRequire(import.meta.url);
141
+ const seamIconPath = require.resolve(
142
+ '@chan.run/design/favicons/seam/android-chrome-512x512.png',
143
+ );
144
+
145
+ export default defineConfig({
146
+ modules: ['@wxt-dev/module-react', '@wxt-dev/auto-icons'],
147
+ autoIcons: { baseIconPath: seamIconPath },
148
+ });
149
+ ```
150
+
151
+ See `products/seam/seam-extension/wxt.config.ts` for the working example.
152
+
153
+ ## Adding a new product mark
154
+
155
+ 1. Add to `src/products/<name>/<Name>Icon.tsx` and `<Name>Wordmark.tsx`. Use `ProductIcon` / `ProductWordmark` from `src/products/_internal.tsx` — they encode the locked visual rules so you only specify the letter splits.
156
+ 2. Register in `src/products/index.ts` (`PRODUCT_ICONS`, `PRODUCT_WORDMARKS`).
157
+ 3. Add an entry to `scripts/export-svg.ts` (`PRODUCTS` array) and `scripts/gen-favicons.ts` (`MARKS` array) so SVG/favicon variants are generated.
158
+ 4. Run `pnpm gen-brand` to produce the `assets/` outputs.
159
+ 5. Run `pnpm typecheck && pnpm exec biome check .` to confirm.
160
+
161
+ See `docs/brand-marks.md` for the full hard-rules reference.
162
+
163
+ ## Repo layout
164
+
165
+ ```
166
+ packages/design/
167
+ ├── src/
168
+ │ ├── brand/ # CHAN[•]RUN umbrella wordmark
169
+ │ ├── components/ # Splash, Topbar, slides, ColorModeButton, …
170
+ │ ├── favicons/ # Per-product head components + asset URL maps
171
+ │ ├── products/ # Per-product Icon + Wordmark components
172
+ │ │ ├── _internal.tsx # Locked visual primitives (do not export)
173
+ │ │ ├── <name>/ # ensure | entropy | restunnel | seam | slides
174
+ │ │ └── index.ts # Exports + PRODUCT_ICONS / PRODUCT_WORDMARKS
175
+ │ ├── styled-system/ # Chakra typegen output (do not edit by hand)
176
+ │ ├── theme.ts # `system` — Chakra v3 theme config
177
+ │ ├── TokenShowcase.tsx # Visual reference for all tokens
178
+ │ └── index.ts
179
+ ├── scripts/
180
+ │ ├── export-svg.ts # Wordmarks/icons/favicon-source SVGs → assets/svg/
181
+ │ └── gen-favicons.ts # Per-product favicon bundles → assets/favicons/
182
+ └── assets/ # Committed build outputs (subpath-exported)
183
+ ├── svg/
184
+ └── favicons/<name>/
185
+ ```
186
+
187
+ ## Scripts
188
+
189
+ | Command | What |
190
+ | ------------------------- | --------------------------------------------------------------- |
191
+ | `pnpm build` | Build TS bundle into `dist/` (consumed by published package) |
192
+ | `pnpm typecheck` | `tsc --noEmit` for `src/` and `scripts/` |
193
+ | `pnpm gen-brand` | Regenerate all SVG and favicon assets into `assets/` |
194
+ | `pnpm export-svg` | Just the SVGs |
195
+ | `pnpm gen-favicons` | Just the favicon bundles (depends on SVGs being generated) |
package/dist/index.d.mts CHANGED
@@ -1,6 +1,4 @@
1
- import * as _$_chakra_ui_react0 from "@chakra-ui/react";
2
1
  import { FlexProps, TextProps } from "@chakra-ui/react";
3
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
4
2
  import { ComponentType, ReactNode } from "react";
5
3
 
6
4
  //#region src/brand/Wordmark.d.ts
@@ -15,10 +13,10 @@ declare function Wordmark({
15
13
  color,
16
14
  pulseDot,
17
15
  ...rest
18
- }: WordmarkProps): _$react_jsx_runtime0.JSX.Element;
16
+ }: WordmarkProps): import("react/jsx-runtime").JSX.Element;
19
17
  //#endregion
20
18
  //#region src/components/ColorModeButton.d.ts
21
- declare function ColorModeButton(props: FlexProps): _$react_jsx_runtime0.JSX.Element;
19
+ declare function ColorModeButton(props: FlexProps): import("react/jsx-runtime").JSX.Element;
22
20
  //#endregion
23
21
  //#region src/components/Splash.d.ts
24
22
  interface SplashProps {
@@ -42,7 +40,7 @@ declare function Splash({
42
40
  size,
43
41
  holdMs,
44
42
  revealMs
45
- }: SplashProps): _$react_jsx_runtime0.JSX.Element | null;
43
+ }: SplashProps): import("react/jsx-runtime").JSX.Element | null;
46
44
  //#endregion
47
45
  //#region src/components/slides/types.d.ts
48
46
  /** Supported slide layout templates */
@@ -112,7 +110,7 @@ declare function DeckCard({
112
110
  tags,
113
111
  slideCount,
114
112
  onClick
115
- }: DeckCardProps): _$react_jsx_runtime0.JSX.Element;
113
+ }: DeckCardProps): import("react/jsx-runtime").JSX.Element;
116
114
  //#endregion
117
115
  //#region src/components/slides/SlideComponents.d.ts
118
116
  type ComponentMap = Record<string, React.ComponentType<Record<string, unknown>>>;
@@ -135,13 +133,13 @@ declare function SlideControls({
135
133
  onSlideChange,
136
134
  onTogglePresent,
137
135
  isPresenting
138
- }: SlideControlsProps): _$react_jsx_runtime0.JSX.Element;
136
+ }: SlideControlsProps): import("react/jsx-runtime").JSX.Element;
139
137
  //#endregion
140
138
  //#region src/components/slides/SlideLayout.d.ts
141
139
  declare function SlideLayoutComponent({
142
140
  layout,
143
141
  children
144
- }: SlideProps): _$react_jsx_runtime0.JSX.Element;
142
+ }: SlideProps): import("react/jsx-runtime").JSX.Element;
145
143
  //#endregion
146
144
  //#region src/components/slides/SlideViewer.d.ts
147
145
  declare function SlideViewer({
@@ -150,7 +148,7 @@ declare function SlideViewer({
150
148
  onSlideChange,
151
149
  isPresenting,
152
150
  onTogglePresent
153
- }: SlideViewerProps): _$react_jsx_runtime0.JSX.Element | null;
151
+ }: SlideViewerProps): import("react/jsx-runtime").JSX.Element | null;
154
152
  //#endregion
155
153
  //#region src/components/Topbar.d.ts
156
154
  interface TopbarProps {
@@ -159,7 +157,7 @@ interface TopbarProps {
159
157
  }
160
158
  declare function Topbar({
161
159
  onMenuOpen
162
- }: TopbarProps): _$react_jsx_runtime0.JSX.Element;
160
+ }: TopbarProps): import("react/jsx-runtime").JSX.Element;
163
161
  //#endregion
164
162
  //#region src/favicons/index.d.ts
165
163
  interface MarkAssets {
@@ -181,15 +179,15 @@ declare const entropyFaviconAssets: MarkAssets;
181
179
  declare const restunnelFaviconAssets: MarkAssets;
182
180
  declare const seamFaviconAssets: MarkAssets;
183
181
  declare const slidesFaviconAssets: MarkAssets;
184
- declare function ChanFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
185
- declare function EnsureFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
186
- declare function EntropyFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
187
- declare function RestunnelFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
188
- declare function SeamFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
189
- declare function SlidesFaviconHead(props?: FaviconHeadProps): _$react_jsx_runtime0.JSX.Element;
182
+ declare function ChanFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
183
+ declare function EnsureFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
184
+ declare function EntropyFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
185
+ declare function RestunnelFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
186
+ declare function SeamFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
187
+ declare function SlidesFaviconHead(props?: FaviconHeadProps): import("react/jsx-runtime").JSX.Element;
190
188
  //#endregion
191
189
  //#region src/products/ensure/EnsureIcon.d.ts
192
- declare function EnsureIcon(): _$react_jsx_runtime0.JSX.Element;
190
+ declare function EnsureIcon(): import("react/jsx-runtime").JSX.Element;
193
191
  //#endregion
194
192
  //#region src/products/_internal.d.ts
195
193
  interface ProductWordmarkProps extends Omit<TextProps, "children"> {
@@ -203,44 +201,44 @@ interface ProductWordmarkProps extends Omit<TextProps, "children"> {
203
201
  //#endregion
204
202
  //#region src/products/ensure/EnsureWordmark.d.ts
205
203
  interface EnsureWordmarkProps extends Omit<ProductWordmarkProps, "prefix" | "terminal"> {}
206
- declare function EnsureWordmark(props: EnsureWordmarkProps): _$react_jsx_runtime0.JSX.Element;
204
+ declare function EnsureWordmark(props: EnsureWordmarkProps): import("react/jsx-runtime").JSX.Element;
207
205
  //#endregion
208
206
  //#region src/products/entropy/EntropyIcon.d.ts
209
- declare function EntropyIcon(): _$react_jsx_runtime0.JSX.Element;
207
+ declare function EntropyIcon(): import("react/jsx-runtime").JSX.Element;
210
208
  //#endregion
211
209
  //#region src/products/entropy/EntropyWordmark.d.ts
212
210
  interface EntropyWordmarkProps extends Omit<ProductWordmarkProps, "prefix" | "terminal"> {}
213
- declare function EntropyWordmark(props: EntropyWordmarkProps): _$react_jsx_runtime0.JSX.Element;
211
+ declare function EntropyWordmark(props: EntropyWordmarkProps): import("react/jsx-runtime").JSX.Element;
214
212
  //#endregion
215
213
  //#region src/products/restunnel/RestunnelIcon.d.ts
216
- declare function RestunnelIcon(): _$react_jsx_runtime0.JSX.Element;
214
+ declare function RestunnelIcon(): import("react/jsx-runtime").JSX.Element;
217
215
  //#endregion
218
216
  //#region src/products/restunnel/RestunnelWordmark.d.ts
219
217
  interface RestunnelWordmarkProps extends Omit<ProductWordmarkProps, "prefix" | "terminal"> {}
220
- declare function RestunnelWordmark(props: RestunnelWordmarkProps): _$react_jsx_runtime0.JSX.Element;
218
+ declare function RestunnelWordmark(props: RestunnelWordmarkProps): import("react/jsx-runtime").JSX.Element;
221
219
  //#endregion
222
220
  //#region src/products/seam/SeamIcon.d.ts
223
- declare function SeamIcon(): _$react_jsx_runtime0.JSX.Element;
221
+ declare function SeamIcon(): import("react/jsx-runtime").JSX.Element;
224
222
  //#endregion
225
223
  //#region src/products/seam/SeamWordmark.d.ts
226
224
  interface SeamWordmarkProps extends Omit<ProductWordmarkProps, "prefix" | "terminal"> {}
227
- declare function SeamWordmark(props: SeamWordmarkProps): _$react_jsx_runtime0.JSX.Element;
225
+ declare function SeamWordmark(props: SeamWordmarkProps): import("react/jsx-runtime").JSX.Element;
228
226
  //#endregion
229
227
  //#region src/products/slides/SlidesIcon.d.ts
230
- declare function SlidesIcon(): _$react_jsx_runtime0.JSX.Element;
228
+ declare function SlidesIcon(): import("react/jsx-runtime").JSX.Element;
231
229
  //#endregion
232
230
  //#region src/products/slides/SlidesWordmark.d.ts
233
231
  interface SlidesWordmarkProps extends Omit<ProductWordmarkProps, "prefix" | "terminal"> {}
234
- declare function SlidesWordmark(props: SlidesWordmarkProps): _$react_jsx_runtime0.JSX.Element;
232
+ declare function SlidesWordmark(props: SlidesWordmarkProps): import("react/jsx-runtime").JSX.Element;
235
233
  //#endregion
236
234
  //#region src/products/index.d.ts
237
235
  declare const PRODUCT_ICONS: Record<string, ComponentType>;
238
236
  declare const PRODUCT_WORDMARKS: Record<string, ComponentType>;
239
237
  //#endregion
240
238
  //#region src/TokenShowcase.d.ts
241
- declare function TokenShowcase(): _$react_jsx_runtime0.JSX.Element;
239
+ declare function TokenShowcase(): import("react/jsx-runtime").JSX.Element;
242
240
  //#endregion
243
241
  //#region src/theme.d.ts
244
- declare const system: _$_chakra_ui_react0.SystemContext;
242
+ declare const system: import("@chakra-ui/react").SystemContext;
245
243
  //#endregion
246
244
  export { ChanFaviconHead, ColorModeButton, DeckCard, type DeckCardProps, EnsureFaviconHead, EnsureIcon, EnsureWordmark, type EnsureWordmarkProps, EntropyFaviconHead, EntropyIcon, EntropyWordmark, type EntropyWordmarkProps, PRODUCT_ICONS, PRODUCT_WORDMARKS, RestunnelFaviconHead, RestunnelIcon, RestunnelWordmark, type RestunnelWordmarkProps, SeamFaviconHead, SeamIcon, SeamWordmark, type SeamWordmarkProps, SlideControls, type SlideControlsProps, type SlideEntry, SlideLayoutComponent as SlideLayout, type SlideLayout as SlideLayoutType, type SlideProps, SlideViewer, type SlideViewerProps, SlidesFaviconHead, SlidesIcon, SlidesWordmark, type SlidesWordmarkProps, Splash, type SplashProps, TokenShowcase, Topbar, type TopbarProps, Wordmark, type WordmarkProps, chanFaviconAssets, ensureFaviconAssets, entropyFaviconAssets, getSlideComponents, restunnelFaviconAssets, seamFaviconAssets, slidesFaviconAssets, system };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { Badge, Box, Card, Center, Code, Flex, Grid, HStack, Heading, IconButton, Text, createSystem, defaultConfig, defineConfig, defineSlotRecipe, useSlotRecipe } from "@chakra-ui/react";
1
+ import { Badge, Box, Card, Center, Code, Flex, Grid, HStack, Heading, IconButton, Text, createSystem, defaultConfig, defineConfig, defineRecipe, defineSlotRecipe, useSlotRecipe } from "@chakra-ui/react";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useTheme } from "next-themes";
4
4
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -1985,6 +1985,322 @@ function TokenShowcase() {
1985
1985
  ]
1986
1986
  });
1987
1987
  }
1988
+ const adminChromeSlotRecipe = defineSlotRecipe({
1989
+ className: "chan-admin-chrome",
1990
+ slots: [
1991
+ "rail",
1992
+ "frame",
1993
+ "tick",
1994
+ "tag"
1995
+ ],
1996
+ base: {
1997
+ rail: {
1998
+ position: "sticky",
1999
+ top: "52px",
2000
+ zIndex: 54,
2001
+ display: "flex",
2002
+ alignItems: "center",
2003
+ gap: "9px",
2004
+ height: "26px",
2005
+ px: "22px",
2006
+ fontFamily: "body",
2007
+ fontSize: "9px",
2008
+ letterSpacing: "0.18em",
2009
+ textTransform: "uppercase",
2010
+ color: "chan.orange",
2011
+ bg: "chan.bg",
2012
+ borderBottom: "1px solid",
2013
+ borderColor: "chan.orange.border",
2014
+ backgroundImage: "repeating-linear-gradient(135deg, transparent, transparent 9px, rgba(255,77,0,.06) 9px, rgba(255,77,0,.06) 18px)"
2015
+ },
2016
+ frame: {
2017
+ position: "fixed",
2018
+ inset: 0,
2019
+ zIndex: 80,
2020
+ pointerEvents: "none",
2021
+ border: "1.5px solid",
2022
+ borderColor: "chan.orange.border"
2023
+ },
2024
+ tick: {
2025
+ position: "absolute",
2026
+ width: "16px",
2027
+ height: "16px",
2028
+ border: "2px solid",
2029
+ borderColor: "chan.orange"
2030
+ },
2031
+ tag: {
2032
+ position: "absolute",
2033
+ left: "50%",
2034
+ bottom: 0,
2035
+ transform: "translateX(-50%)",
2036
+ fontFamily: "body",
2037
+ fontSize: "8px",
2038
+ letterSpacing: "0.2em",
2039
+ textTransform: "uppercase",
2040
+ color: "chan.orange",
2041
+ whiteSpace: "nowrap",
2042
+ bg: "chan.bg",
2043
+ border: "1px solid",
2044
+ borderColor: "chan.orange.border",
2045
+ borderBottom: 0,
2046
+ borderTopRadius: "4px",
2047
+ px: "12px",
2048
+ pt: "4px",
2049
+ pb: "5px"
2050
+ }
2051
+ }
2052
+ });
2053
+ //#endregion
2054
+ //#region src/components/dataTable.recipe.ts
2055
+ const dataTableSlotRecipe = defineSlotRecipe({
2056
+ className: "chan-data-table",
2057
+ slots: [
2058
+ "root",
2059
+ "header",
2060
+ "columnHeader",
2061
+ "row",
2062
+ "cell"
2063
+ ],
2064
+ base: {
2065
+ root: {
2066
+ border: "1px solid",
2067
+ borderColor: "chan.border",
2068
+ borderRadius: "md",
2069
+ bg: "chan.bg",
2070
+ overflow: "hidden",
2071
+ fontFamily: "body"
2072
+ },
2073
+ header: { bg: "chan.bg.code" },
2074
+ columnHeader: {
2075
+ fontSize: "8px",
2076
+ letterSpacing: "0.22em",
2077
+ textTransform: "uppercase",
2078
+ color: "chan.text.faint",
2079
+ borderColor: "chan.border",
2080
+ py: "11px"
2081
+ },
2082
+ row: { cursor: "default" },
2083
+ cell: {
2084
+ fontSize: "11px",
2085
+ borderColor: "chan.border",
2086
+ borderBottomStyle: "dashed"
2087
+ }
2088
+ },
2089
+ variants: {
2090
+ archived: { true: { row: { opacity: .5 } } },
2091
+ clickable: { true: { row: { cursor: "pointer" } } }
2092
+ }
2093
+ });
2094
+ //#endregion
2095
+ //#region src/components/lbl.recipe.ts
2096
+ const toneColor = (color) => ({ color });
2097
+ const lblRecipe = defineRecipe({
2098
+ className: "chan-lbl",
2099
+ base: {
2100
+ display: "inline-flex",
2101
+ alignItems: "center",
2102
+ fontFamily: "body",
2103
+ fontSize: "9.5px",
2104
+ letterSpacing: "0.18em",
2105
+ textTransform: "uppercase"
2106
+ },
2107
+ variants: {
2108
+ tone: {
2109
+ orange: toneColor("chan.orange"),
2110
+ dim: toneColor("chan.text.dim"),
2111
+ faint: toneColor("chan.text.faint"),
2112
+ ok: toneColor("chan.green"),
2113
+ warn: toneColor("chan.yellow"),
2114
+ danger: toneColor("chan.danger"),
2115
+ info: toneColor("#6a9eda"),
2116
+ neutral: toneColor("chan.text.dim"),
2117
+ owner: toneColor("chan.orange"),
2118
+ admin: toneColor("#6a9eda"),
2119
+ member: toneColor("chan.text.dim"),
2120
+ off: toneColor("chan.text.faint")
2121
+ },
2122
+ variant: {
2123
+ plain: {},
2124
+ chip: {
2125
+ fontSize: "8.5px",
2126
+ letterSpacing: "0.14em",
2127
+ border: "1px solid",
2128
+ borderColor: "color-mix(in srgb, currentColor 38%, transparent)",
2129
+ bg: "color-mix(in srgb, currentColor 12%, transparent)",
2130
+ borderRadius: "3px",
2131
+ px: "7px",
2132
+ py: "2px"
2133
+ }
2134
+ }
2135
+ },
2136
+ compoundVariants: [{
2137
+ tone: "off",
2138
+ variant: "chip",
2139
+ css: {
2140
+ borderStyle: "dashed",
2141
+ bg: "transparent"
2142
+ }
2143
+ }],
2144
+ defaultVariants: {
2145
+ tone: "neutral",
2146
+ variant: "plain"
2147
+ }
2148
+ });
2149
+ //#endregion
2150
+ //#region src/components/panel.recipe.ts
2151
+ const panelSlotRecipe = defineSlotRecipe({
2152
+ className: "chan-panel",
2153
+ slots: [
2154
+ "root",
2155
+ "head",
2156
+ "label",
2157
+ "body"
2158
+ ],
2159
+ base: {
2160
+ root: {
2161
+ border: "1px solid",
2162
+ borderColor: "chan.border",
2163
+ borderRadius: "md",
2164
+ bg: "chan.bg",
2165
+ overflow: "hidden"
2166
+ },
2167
+ head: {
2168
+ display: "flex",
2169
+ alignItems: "center",
2170
+ gap: "8px",
2171
+ px: "16px",
2172
+ py: "11px",
2173
+ borderBottom: "1px solid",
2174
+ borderColor: "chan.border"
2175
+ },
2176
+ label: {
2177
+ fontFamily: "body",
2178
+ fontSize: "8.5px",
2179
+ letterSpacing: "0.18em",
2180
+ textTransform: "uppercase",
2181
+ color: "chan.text.dim"
2182
+ },
2183
+ body: {
2184
+ px: "16px",
2185
+ py: "14px"
2186
+ }
2187
+ },
2188
+ variants: { state: {
2189
+ default: {},
2190
+ live: { label: { color: "chan.orange" } },
2191
+ degraded: { root: { borderColor: "chan.danger.border" } },
2192
+ placeholder: { root: {
2193
+ borderStyle: "dashed",
2194
+ opacity: .6
2195
+ } }
2196
+ } },
2197
+ defaultVariants: { state: "default" }
2198
+ });
2199
+ //#endregion
2200
+ //#region src/components/splash.recipe.ts
2201
+ const splashSlotRecipe = defineSlotRecipe({
2202
+ className: "chan-splash",
2203
+ slots: [
2204
+ "root",
2205
+ "panelStart",
2206
+ "panelEnd",
2207
+ "hub"
2208
+ ],
2209
+ base: {
2210
+ root: {
2211
+ position: "fixed",
2212
+ inset: 0,
2213
+ display: "flex",
2214
+ zIndex: 1e4
2215
+ },
2216
+ panelStart: {
2217
+ flex: 1,
2218
+ position: "relative",
2219
+ bg: "chan.bg.soft",
2220
+ transitionProperty: "top, left",
2221
+ transitionTimingFunction: "ease-in-out"
2222
+ },
2223
+ panelEnd: {
2224
+ flex: 1,
2225
+ position: "relative",
2226
+ bg: "chan.bg.soft",
2227
+ transitionProperty: "bottom, right",
2228
+ transitionTimingFunction: "ease-in-out"
2229
+ },
2230
+ hub: {
2231
+ position: "absolute",
2232
+ bg: "chan.bg.card",
2233
+ border: "1px solid",
2234
+ borderColor: "chan.border",
2235
+ borderRadius: "md",
2236
+ display: "flex",
2237
+ alignItems: "center",
2238
+ justifyContent: "center",
2239
+ zIndex: 1,
2240
+ width: "var(--splash-hub-w)"
2241
+ }
2242
+ },
2243
+ variants: {
2244
+ orientation: {
2245
+ vertical: {
2246
+ root: { flexDirection: "column" },
2247
+ panelStart: {
2248
+ borderBottom: "2px solid",
2249
+ borderColor: "chan.border"
2250
+ },
2251
+ panelEnd: {
2252
+ borderTop: "2px solid",
2253
+ borderColor: "chan.border"
2254
+ },
2255
+ hub: {
2256
+ bottom: 0,
2257
+ left: "50%",
2258
+ transform: "translate(-50%, 50%)"
2259
+ }
2260
+ },
2261
+ horizontal: {
2262
+ root: { flexDirection: "row" },
2263
+ panelStart: {
2264
+ borderRight: "2px solid",
2265
+ borderColor: "chan.border"
2266
+ },
2267
+ panelEnd: {
2268
+ borderLeft: "2px solid",
2269
+ borderColor: "chan.border"
2270
+ },
2271
+ hub: {
2272
+ right: 0,
2273
+ top: "50%",
2274
+ transform: "translate(50%, -50%)"
2275
+ }
2276
+ }
2277
+ },
2278
+ size: {
2279
+ sm: { hub: {
2280
+ "--splash-hub-w": "180px",
2281
+ px: 4,
2282
+ py: 3
2283
+ } },
2284
+ md: { hub: {
2285
+ "--splash-hub-w": "240px",
2286
+ px: 5,
2287
+ py: 4
2288
+ } },
2289
+ lg: { hub: {
2290
+ "--splash-hub-w": "320px",
2291
+ px: 6,
2292
+ py: 5
2293
+ } }
2294
+ }
2295
+ },
2296
+ defaultVariants: {
2297
+ orientation: "vertical",
2298
+ size: "md"
2299
+ }
2300
+ });
2301
+ //#endregion
2302
+ //#region src/components/statusDot.recipe.ts
2303
+ const dot = (color) => ({ bg: color });
1988
2304
  const system = createSystem(defaultConfig, defineConfig({
1989
2305
  globalCss: { body: {
1990
2306
  bg: "{colors.chan.bg}",
@@ -2028,6 +2344,9 @@ const system = createSystem(defaultConfig, defineConfig({
2028
2344
  "chan.green": { value: "#3a8c5c" },
2029
2345
  "chan.green.bg": { value: "rgba(58, 140, 92, 0.08)" },
2030
2346
  "chan.green.border": { value: "rgba(58, 140, 92, 0.20)" },
2347
+ "chan.teal": { value: "#2f9c9c" },
2348
+ "chan.teal.bg": { value: "rgba(47, 156, 156, 0.08)" },
2349
+ "chan.teal.border": { value: "rgba(47, 156, 156, 0.20)" },
2031
2350
  "chan.yellow": { value: "#a88000" },
2032
2351
  "chan.yellow.bg": { value: "rgba(200, 160, 0, 0.07)" },
2033
2352
  "chan.yellow.border": { value: "rgba(200, 160, 0, 0.15)" },
@@ -2123,106 +2442,37 @@ const system = createSystem(defaultConfig, defineConfig({
2123
2442
  textTransform: "uppercase"
2124
2443
  } }
2125
2444
  },
2126
- slotRecipes: { splash: defineSlotRecipe({
2127
- className: "chan-splash",
2128
- slots: [
2129
- "root",
2130
- "panelStart",
2131
- "panelEnd",
2132
- "hub"
2133
- ],
2134
- base: {
2135
- root: {
2136
- position: "fixed",
2137
- inset: 0,
2138
- display: "flex",
2139
- zIndex: 1e4
2445
+ recipes: {
2446
+ lbl: lblRecipe,
2447
+ statusDot: defineRecipe({
2448
+ className: "chan-status-dot",
2449
+ base: {
2450
+ display: "inline-block",
2451
+ width: "6px",
2452
+ height: "6px",
2453
+ borderRadius: "full",
2454
+ flexShrink: 0
2140
2455
  },
2141
- panelStart: {
2142
- flex: 1,
2143
- position: "relative",
2144
- bg: "chan.bg.soft",
2145
- transitionProperty: "top, left",
2146
- transitionTimingFunction: "ease-in-out"
2147
- },
2148
- panelEnd: {
2149
- flex: 1,
2150
- position: "relative",
2151
- bg: "chan.bg.soft",
2152
- transitionProperty: "bottom, right",
2153
- transitionTimingFunction: "ease-in-out"
2154
- },
2155
- hub: {
2156
- position: "absolute",
2157
- bg: "chan.bg.card",
2158
- border: "1px solid",
2159
- borderColor: "chan.border",
2160
- borderRadius: "md",
2161
- display: "flex",
2162
- alignItems: "center",
2163
- justifyContent: "center",
2164
- zIndex: 1,
2165
- width: "var(--splash-hub-w)"
2166
- }
2167
- },
2168
- variants: {
2169
- orientation: {
2170
- vertical: {
2171
- root: { flexDirection: "column" },
2172
- panelStart: {
2173
- borderBottom: "2px solid",
2174
- borderColor: "chan.border"
2175
- },
2176
- panelEnd: {
2177
- borderTop: "2px solid",
2178
- borderColor: "chan.border"
2179
- },
2180
- hub: {
2181
- bottom: 0,
2182
- left: "50%",
2183
- transform: "translate(-50%, 50%)"
2184
- }
2456
+ variants: {
2457
+ tone: {
2458
+ ok: dot("chan.green"),
2459
+ warn: dot("chan.yellow"),
2460
+ danger: dot("chan.danger"),
2461
+ info: dot("#6a9eda"),
2462
+ neutral: dot("chan.text.faint"),
2463
+ off: dot("chan.text.faint")
2185
2464
  },
2186
- horizontal: {
2187
- root: { flexDirection: "row" },
2188
- panelStart: {
2189
- borderRight: "2px solid",
2190
- borderColor: "chan.border"
2191
- },
2192
- panelEnd: {
2193
- borderLeft: "2px solid",
2194
- borderColor: "chan.border"
2195
- },
2196
- hub: {
2197
- right: 0,
2198
- top: "50%",
2199
- transform: "translate(50%, -50%)"
2200
- }
2201
- }
2465
+ live: { true: { animation: "chan-pulse" } }
2202
2466
  },
2203
- size: {
2204
- sm: { hub: {
2205
- "--splash-hub-w": "180px",
2206
- px: 4,
2207
- py: 3
2208
- } },
2209
- md: { hub: {
2210
- "--splash-hub-w": "240px",
2211
- px: 5,
2212
- py: 4
2213
- } },
2214
- lg: { hub: {
2215
- "--splash-hub-w": "320px",
2216
- px: 6,
2217
- py: 5
2218
- } }
2219
- }
2220
- },
2221
- defaultVariants: {
2222
- orientation: "vertical",
2223
- size: "md"
2224
- }
2225
- }) },
2467
+ defaultVariants: { tone: "neutral" }
2468
+ })
2469
+ },
2470
+ slotRecipes: {
2471
+ splash: splashSlotRecipe,
2472
+ panel: panelSlotRecipe,
2473
+ dataTable: dataTableSlotRecipe,
2474
+ adminChrome: adminChromeSlotRecipe
2475
+ },
2226
2476
  semanticTokens: { colors: {
2227
2477
  "chan.bg": { value: {
2228
2478
  _light: "{colors.chan.palette.light.bg}",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chan.run/design",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared Chakra UI v3 theme and component library for chan.run",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,9 +19,9 @@
19
19
  "assets"
20
20
  ],
21
21
  "peerDependencies": {
22
- "@chakra-ui/react": "3.35.0",
22
+ "@chakra-ui/react": "^3.35.0",
23
23
  "next-themes": "*",
24
- "react": "19.2.4"
24
+ "react": "^19.0.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@chakra-ui/cli": "3.35.0",
@@ -29,9 +29,9 @@
29
29
  "@types/react": "19.2.14",
30
30
  "favicons": "^7.2.0",
31
31
  "opentype.js": "^1.3.4",
32
- "tsdown": "^0.21.7",
32
+ "tsdown": "^0.22.3",
33
33
  "tsx": "^4.21.0",
34
- "typescript": "5.9.3"
34
+ "typescript": "6.0.3"
35
35
  },
36
36
  "repository": {
37
37
  "type": "git",