@arronqzy/ui 1.0.9 → 1.0.11

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 CHANGED
@@ -1,15 +1,12 @@
1
1
  # @arronqzy/ui
2
2
 
3
- Abuilder 的**共享 UI 组件库**,基于 [shadcn/ui](https://ui.shadcn.com/) + Radix UI + Tailwind CSS,为编辑器与蓝图提供统一视觉与交互。
3
+ React 编辑器和蓝图用的组件:Button、Dialog、Menu、Toast、可调面板等。基于 Radix 和 Tailwind,视觉和 `@arronqzy/tailwind` 的令牌一致。
4
4
 
5
- ## 功能概览
5
+ 这是编辑器内部 UI,不是给业务大屏用的组件库。大屏上的图表、表格、三维在 `react-view` 的物料里。
6
6
 
7
- - **基础组件**:Button、Input、Dialog、Dropdown、Tabs、Tooltip、Toast 等
8
- - **表单**:与 `react-hook-form`、`zod` 集成的表单控件
9
- - **布局**:Resizable Panels、ScrollArea、Separator
10
- - **主题**:`ThemeProvider` 支持亮/暗色与 `class` 策略
11
- - **层级管理**:`UI_Z_INDEX` / `MODAL_Z_INDEX` 统一浮层 z-index
12
- - **图表**:封装 Recharts 常用图表(按需使用)
7
+ 下图顶栏、侧栏、按钮都来自本包:
8
+
9
+ ![编辑器界面由本包的菜单、按钮和面板拼出来](https://github.com/qianzhuoyao/APanel5/raw/v5/docs/guide-assets/live-zh/01-overview.png)
13
10
 
14
11
  ## 安装
15
12
 
@@ -21,7 +18,7 @@ pnpm add @arronqzy/ui
21
18
  import "@arronqzy/ui/styles.css";
22
19
  ```
23
20
 
24
- ## 使用
21
+ ## 用法
25
22
 
26
23
  ```tsx
27
24
  import { ThemeProvider, Button, toast } from "@arronqzy/ui";
@@ -36,27 +33,27 @@ function App() {
36
33
  }
37
34
  ```
38
35
 
39
- ### 工具函数
40
-
41
36
  ```ts
42
- import { cn } from "@arronqzy/ui";
43
- import { UI_Z_INDEX } from "@arronqzy/ui";
37
+ import { cn, UI_Z_INDEX } from "@arronqzy/ui";
44
38
  ```
45
39
 
46
- ## 导出说明
40
+ 浮层 z-index 用 `UI_Z_INDEX` / `MODAL_Z_INDEX`,不要在蓝图或配置面板里另写一个魔法数字,否则菜单会被画布盖住。
41
+
42
+ ## 导出
47
43
 
48
- - `@arronqzy/ui` — 全部组件、`cn`、`ThemeProvider`、`useToast`
49
- - `@arronqzy/ui/styles.css` — 编译后的 Tailwind 样式
44
+ - `@arronqzy/ui` — 组件、`cn`、`ThemeProvider`、`useToast`
45
+ - `@arronqzy/ui/styles.css` — 编好的 Tailwind
50
46
 
51
- 组件源码位于 `src/ui/`,遵循 shadcn 可组合模式,可按需在本包内扩展。
47
+ 源码在 `src/ui/`,按 shadcn 的方式组合。加组件时放进这个包,不要在 `react-view` 里复制一份 Dialog。
52
48
 
53
49
  ## 构建样式
54
50
 
55
51
  ```bash
56
52
  pnpm -C packages/ui build:css
53
+ pnpm -C packages/ui dev:css
57
54
  ```
58
55
 
59
- 依赖 `@arronqzy/tailwind` 预设保持与设计令牌一致。
56
+ `apps/web` 的 `predev` 会构建一次。改组件 class 时开 `dev:css`,否则演示页看不到样式变化。
60
57
 
61
58
  ## 许可证
62
59
 
package/dist/styles.css CHANGED
@@ -566,13 +566,13 @@ video {
566
566
  --secondary: 210 40% 96.1%;
567
567
  --secondary-foreground: 222.2 47.4% 11.2%;
568
568
  --muted: 210 40% 96.1%;
569
- --muted-foreground: 215.4 16.3% 46.9%;
569
+ --muted-foreground: 215.4 16.7% 45.7%;
570
570
  --accent: 210 40% 96.1%;
571
571
  --accent-foreground: 222.2 47.4% 11.2%;
572
- --destructive: 0 84.2% 60.2%;
573
- --destructive-foreground: 210 40% 98%;
572
+ --destructive: 358.6 72.7% 53.5%;
573
+ --destructive-foreground: 0 0% 100%;
574
574
  --border: 214.3 31.8% 91.4%;
575
- --input: 214.3 31.8% 91.4%;
575
+ --input: 214.3 15.9% 83%;
576
576
  --ring: 222.2 84% 4.9%;
577
577
  --radius: 0.5rem;
578
578
  }
@@ -592,10 +592,10 @@ video {
592
592
  --muted-foreground: 215 20.2% 65.1%;
593
593
  --accent: 217.2 32.6% 17.5%;
594
594
  --accent-foreground: 210 40% 98%;
595
- --destructive: 0 62.8% 30.6%;
596
- --destructive-foreground: 210 40% 98%;
595
+ --destructive: 0 90.6% 70.8%;
596
+ --destructive-foreground: 222.2 47.4% 11.2%;
597
597
  --border: 217.2 32.6% 17.5%;
598
- --input: 217.2 32.6% 17.5%;
598
+ --input: 217.2 28.5% 20.4%;
599
599
  --ring: 212.7 26.8% 83.9%;
600
600
  }
601
601
 
@@ -636,6 +636,10 @@ body {
636
636
  visibility: hidden;
637
637
  }
638
638
 
639
+ .static {
640
+ position: static;
641
+ }
642
+
639
643
  .fixed {
640
644
  position: fixed;
641
645
  }
@@ -1546,10 +1550,6 @@ body {
1546
1550
  border-color: hsl(var(--border) / 0.5);
1547
1551
  }
1548
1552
 
1549
- .border-border\/60 {
1550
- border-color: hsl(var(--border) / 0.6);
1551
- }
1552
-
1553
1553
  .border-border\/70 {
1554
1554
  border-color: hsl(var(--border) / 0.7);
1555
1555
  }
@@ -1594,8 +1594,8 @@ body {
1594
1594
  background-color: hsl(var(--background));
1595
1595
  }
1596
1596
 
1597
- .bg-background\/60 {
1598
- background-color: hsl(var(--background) / 0.6);
1597
+ .bg-background\/80 {
1598
+ background-color: hsl(var(--background) / 0.8);
1599
1599
  }
1600
1600
 
1601
1601
  .bg-black\/80 {
@@ -1606,8 +1606,8 @@ body {
1606
1606
  background-color: hsl(var(--border));
1607
1607
  }
1608
1608
 
1609
- .bg-border\/60 {
1610
- background-color: hsl(var(--border) / 0.6);
1609
+ .bg-border\/40 {
1610
+ background-color: hsl(var(--border) / 0.4);
1611
1611
  }
1612
1612
 
1613
1613
  .bg-card {
@@ -1918,10 +1918,6 @@ body {
1918
1918
  color: hsl(var(--foreground) / 0.5);
1919
1919
  }
1920
1920
 
1921
- .text-foreground\/60 {
1922
- color: hsl(var(--foreground) / 0.6);
1923
- }
1924
-
1925
1921
  .text-muted-foreground {
1926
1922
  color: hsl(var(--muted-foreground));
1927
1923
  }
@@ -1946,6 +1942,10 @@ body {
1946
1942
  text-underline-offset: 4px;
1947
1943
  }
1948
1944
 
1945
+ .accent-foreground {
1946
+ accent-color: hsl(var(--foreground));
1947
+ }
1948
+
1949
1949
  .opacity-0 {
1950
1950
  opacity: 0;
1951
1951
  }
@@ -1999,6 +1999,12 @@ body {
1999
1999
  outline-style: solid;
2000
2000
  }
2001
2001
 
2002
+ .ring {
2003
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2004
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2005
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2006
+ }
2007
+
2002
2008
  .ring-0 {
2003
2009
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2004
2010
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@@ -2121,6 +2127,8 @@ body {
2121
2127
  --cell-size: 2rem;
2122
2128
  }
2123
2129
 
2130
+ /* Static fallback; ThemeProvider overrides these with the chroma-js palette from lib/theme-palette.ts. */
2131
+
2124
2132
  /* 弹窗蒙层全屏覆盖,层级高于画布 Ruler / 工具栏 */
2125
2133
 
2126
2134
  [data-radix-dialog-overlay],
@@ -2182,6 +2190,10 @@ body {
2182
2190
  background-color: hsl(var(--accent));
2183
2191
  }
2184
2192
 
2193
+ .hover\:bg-border\/80:hover {
2194
+ background-color: hsl(var(--border) / 0.8);
2195
+ }
2196
+
2185
2197
  .hover\:bg-destructive\/80:hover {
2186
2198
  background-color: hsl(var(--destructive) / 0.8);
2187
2199
  }
package/index.ts CHANGED
@@ -5,3 +5,4 @@ export * from "./src/lib/utils";
5
5
  export { MODAL_Z_INDEX, UI_Z_INDEX } from "./src/lib/overlay-z-index";
6
6
  export * from "./src/hooks/use-toast";
7
7
  export * from "./src/theme-provider";
8
+ export * from "./src/lib/theme-palette";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/ui",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "exports": {
5
5
  ".": "./index.ts",
6
6
  "./counter": "./components/counter.ts",
@@ -38,6 +38,7 @@
38
38
  "@radix-ui/react-toggle": "^1.1.9",
39
39
  "@radix-ui/react-toggle-group": "^1.1.10",
40
40
  "@radix-ui/react-tooltip": "^1.2.8",
41
+ "chroma-js": "^3.2.0",
41
42
  "class-variance-authority": "^0.7.1",
42
43
  "clsx": "^2.1.1",
43
44
  "cmdk": "^1.1.1",
@@ -58,12 +59,13 @@
58
59
  "zod": "^4.1.9"
59
60
  },
60
61
  "devDependencies": {
62
+ "@types/chroma-js": "^3.1.2",
61
63
  "@types/react": "^19.0.1",
62
64
  "@types/react-dom": "^19.0.2",
63
65
  "eslint": "^8.57.0",
64
66
  "typescript": "^5.5.4",
65
- "@arronqzy/eslint-config": "0.0.0",
66
67
  "@arronqzy/tailwind": "1.0.2",
68
+ "@arronqzy/eslint-config": "0.0.0",
67
69
  "@arronqzy/typescript-config": "1.0.0"
68
70
  },
69
71
  "scripts": {
@@ -0,0 +1,228 @@
1
+ import chroma, { type Color } from "chroma-js";
2
+
3
+ export type ThemeMode = "light" | "dark";
4
+
5
+ export const THEME_TOKENS = [
6
+ "background",
7
+ "foreground",
8
+ "card",
9
+ "card-foreground",
10
+ "popover",
11
+ "popover-foreground",
12
+ "primary",
13
+ "primary-foreground",
14
+ "secondary",
15
+ "secondary-foreground",
16
+ "muted",
17
+ "muted-foreground",
18
+ "accent",
19
+ "accent-foreground",
20
+ "destructive",
21
+ "destructive-foreground",
22
+ "border",
23
+ "input",
24
+ "ring",
25
+ ] as const;
26
+
27
+ export type ThemeToken = (typeof THEME_TOKENS)[number];
28
+
29
+ /** Any color string chroma-js can parse (hex, rgb(), hsl(), named…). */
30
+ export type ThemeColors = Record<ThemeToken, string>;
31
+
32
+ export type ThemePaletteOptions = {
33
+ /** Brand color; `primary-foreground` and `ring` are derived from it. */
34
+ primary?: string | Partial<Record<ThemeMode, string>>;
35
+ overrides?: Partial<Record<ThemeMode, Partial<ThemeColors>>>;
36
+ };
37
+
38
+ export const BASE_THEME_COLORS: Record<ThemeMode, ThemeColors> = {
39
+ light: {
40
+ background: "#ffffff",
41
+ foreground: "#020817",
42
+ card: "#ffffff",
43
+ "card-foreground": "#020817",
44
+ popover: "#ffffff",
45
+ "popover-foreground": "#020817",
46
+ primary: "#0f172a",
47
+ "primary-foreground": "#f8fafc",
48
+ secondary: "#f1f5f9",
49
+ "secondary-foreground": "#0f172a",
50
+ muted: "#f1f5f9",
51
+ "muted-foreground": "#64748b",
52
+ accent: "#f1f5f9",
53
+ "accent-foreground": "#0f172a",
54
+ destructive: "#ef4444",
55
+ "destructive-foreground": "#f8fafc",
56
+ border: "#e2e8f0",
57
+ input: "#e2e8f0",
58
+ ring: "#020817",
59
+ },
60
+ dark: {
61
+ background: "#020817",
62
+ foreground: "#f8fafc",
63
+ card: "#020817",
64
+ "card-foreground": "#f8fafc",
65
+ popover: "#020817",
66
+ "popover-foreground": "#f8fafc",
67
+ primary: "#f8fafc",
68
+ "primary-foreground": "#0f172a",
69
+ secondary: "#1e293b",
70
+ "secondary-foreground": "#f8fafc",
71
+ muted: "#1e293b",
72
+ "muted-foreground": "#94a3b8",
73
+ accent: "#1e293b",
74
+ "accent-foreground": "#f8fafc",
75
+ destructive: "#f87171",
76
+ "destructive-foreground": "#0f172a",
77
+ border: "#1e293b",
78
+ input: "#1e293b",
79
+ ring: "#cbd5e1",
80
+ },
81
+ };
82
+
83
+ type ContrastRule = { token: ThemeToken; against: ThemeToken[]; min: number };
84
+
85
+ /**
86
+ * Applied in order: a token is only adjusted against tokens that are already final,
87
+ * so surfaces come first, then fills, then the text drawn on top of them.
88
+ */
89
+ const CONTRAST_RULES: ContrastRule[] = [
90
+ { token: "foreground", against: ["background"], min: 7 },
91
+ { token: "card-foreground", against: ["card"], min: 7 },
92
+ { token: "popover-foreground", against: ["popover"], min: 7 },
93
+ { token: "primary", against: ["background"], min: 3 },
94
+ { token: "destructive", against: ["background", "card"], min: 4.5 },
95
+ { token: "ring", against: ["background"], min: 3 },
96
+ { token: "primary-foreground", against: ["primary"], min: 4.5 },
97
+ { token: "secondary-foreground", against: ["secondary"], min: 4.5 },
98
+ { token: "accent-foreground", against: ["accent"], min: 4.5 },
99
+ { token: "destructive-foreground", against: ["destructive"], min: 4.5 },
100
+ { token: "muted-foreground", against: ["background", "card", "muted", "accent"], min: 4.5 },
101
+ { token: "border", against: ["background"], min: 1.2 },
102
+ { token: "input", against: ["background"], min: 1.5 },
103
+ ];
104
+
105
+ const LIGHTNESS_STEP = 0.005;
106
+ const CHROMA_STEP = 0.002;
107
+
108
+ function minContrast(color: Color, against: Color[]): number {
109
+ return Math.min(...against.map((bg) => chroma.contrast(color, bg)));
110
+ }
111
+
112
+ /** Drops chroma until the color fits sRGB; clipping would otherwise shift the hue. */
113
+ function inGamutOklch(l: number, c: number, h: number): Color {
114
+ let chromaValue = c;
115
+ let candidate = chroma.oklch(l, chromaValue, h);
116
+ while (candidate.clipped() && chromaValue > 0) {
117
+ chromaValue = Math.max(0, chromaValue - CHROMA_STEP);
118
+ candidate = chroma.oklch(l, chromaValue, h);
119
+ }
120
+ return candidate;
121
+ }
122
+
123
+ function walkLightness(
124
+ color: Color,
125
+ against: Color[],
126
+ min: number,
127
+ direction: 1 | -1,
128
+ ): Color | null {
129
+ const [l, c, h] = color.oklch();
130
+ const hue = Number.isNaN(h) ? 0 : h;
131
+ for (let next = l; next >= 0 && next <= 1; next += direction * LIGHTNESS_STEP) {
132
+ const candidate = inGamutOklch(next, c, hue);
133
+ if (minContrast(candidate, against) >= min) return candidate;
134
+ }
135
+ return null;
136
+ }
137
+
138
+ /**
139
+ * Nudges `color` along OKLCH lightness (hue and chroma kept) until it reaches `min`
140
+ * contrast against every color in `against`. Moves away from the backgrounds first and
141
+ * flips direction only when that side cannot satisfy the target.
142
+ */
143
+ export function ensureContrast(color: string | Color, against: Array<string | Color>, min: number): Color {
144
+ const fg = chroma(color);
145
+ const bgs = against.map((bg) => chroma(bg));
146
+ if (minContrast(fg, bgs) >= min) return fg;
147
+
148
+ const bgLuminance = bgs.reduce((sum, bg) => sum + bg.luminance(), 0) / bgs.length;
149
+ const preferred: 1 | -1 = fg.luminance() >= bgLuminance ? 1 : -1;
150
+ const result =
151
+ walkLightness(fg, bgs, min, preferred) ?? walkLightness(fg, bgs, min, preferred === 1 ? -1 : 1);
152
+ if (result) return result;
153
+
154
+ const extremes = [chroma("#ffffff"), chroma("#000000")];
155
+ return extremes.reduce((best, c) => (minContrast(c, bgs) > minContrast(best, bgs) ? c : best));
156
+ }
157
+
158
+ /** Best of near-white / near-black text for a fill color. */
159
+ function readableOn(fill: Color, light: string, dark: string): string {
160
+ return chroma.contrast(light, fill) >= chroma.contrast(dark, fill) ? light : dark;
161
+ }
162
+
163
+ function resolvePrimary(option: ThemePaletteOptions["primary"], mode: ThemeMode): string | undefined {
164
+ if (!option) return undefined;
165
+ return typeof option === "string" ? option : option[mode];
166
+ }
167
+
168
+ export function createThemeColors(mode: ThemeMode, options: ThemePaletteOptions = {}): Record<ThemeToken, Color> {
169
+ const base = BASE_THEME_COLORS[mode];
170
+ const source: ThemeColors = { ...base };
171
+
172
+ const primary = resolvePrimary(options.primary, mode);
173
+ if (primary && chroma.valid(primary)) {
174
+ source.primary = primary;
175
+ source["primary-foreground"] = readableOn(
176
+ chroma(primary),
177
+ BASE_THEME_COLORS.light["primary-foreground"],
178
+ BASE_THEME_COLORS.dark["primary-foreground"],
179
+ );
180
+ source.ring = primary;
181
+ }
182
+
183
+ for (const [token, value] of Object.entries(options.overrides?.[mode] ?? {})) {
184
+ if (value && chroma.valid(value)) source[token as ThemeToken] = value;
185
+ }
186
+
187
+ const colors = Object.fromEntries(
188
+ THEME_TOKENS.map((token) => [token, chroma(source[token])]),
189
+ ) as Record<ThemeToken, Color>;
190
+
191
+ for (const rule of CONTRAST_RULES) {
192
+ colors[rule.token] = ensureContrast(
193
+ colors[rule.token],
194
+ rule.against.map((token) => colors[token]),
195
+ rule.min,
196
+ );
197
+ }
198
+ return colors;
199
+ }
200
+
201
+ const round = (value: number) => Math.round(value * 10) / 10;
202
+
203
+ /** Formats a color as the `H S% L%` triplet consumed by `hsl(var(--token))`. */
204
+ export function toHslTriplet(color: Color): string {
205
+ const [h, s, l] = color.hsl();
206
+ const hue = Number.isNaN(h) ? 0 : h;
207
+ const sat = Number.isNaN(s) ? 0 : s;
208
+ return `${round(hue)} ${round(sat * 100)}% ${round(l * 100)}%`;
209
+ }
210
+
211
+ export function createThemePalette(mode: ThemeMode, options?: ThemePaletteOptions): Record<ThemeToken, string> {
212
+ const colors = createThemeColors(mode, options);
213
+ return Object.fromEntries(
214
+ THEME_TOKENS.map((token) => [token, toHslTriplet(colors[token])]),
215
+ ) as Record<ThemeToken, string>;
216
+ }
217
+
218
+ function toCssBlock(selector: string, palette: Record<ThemeToken, string>): string {
219
+ const lines = THEME_TOKENS.map((token) => ` --${token}: ${palette[token]};`);
220
+ return `${selector} {\n${lines.join("\n")}\n}`;
221
+ }
222
+
223
+ export function buildThemePaletteCss(options?: ThemePaletteOptions): string {
224
+ return [
225
+ toCssBlock(":root", createThemePalette("light", options)),
226
+ toCssBlock(".dark", createThemePalette("dark", options)),
227
+ ].join("\n");
228
+ }
package/src/styles.css CHANGED
@@ -2,6 +2,7 @@
2
2
  @tailwind components;
3
3
  @tailwind utilities;
4
4
 
5
+ /* Static fallback; ThemeProvider overrides these with the chroma-js palette from lib/theme-palette.ts. */
5
6
  @layer base {
6
7
  :root {
7
8
  --background: 0 0% 100%;
@@ -20,16 +21,16 @@
20
21
  --secondary-foreground: 222.2 47.4% 11.2%;
21
22
 
22
23
  --muted: 210 40% 96.1%;
23
- --muted-foreground: 215.4 16.3% 46.9%;
24
+ --muted-foreground: 215.4 16.7% 45.7%;
24
25
 
25
26
  --accent: 210 40% 96.1%;
26
27
  --accent-foreground: 222.2 47.4% 11.2%;
27
28
 
28
- --destructive: 0 84.2% 60.2%;
29
- --destructive-foreground: 210 40% 98%;
29
+ --destructive: 358.6 72.7% 53.5%;
30
+ --destructive-foreground: 0 0% 100%;
30
31
 
31
32
  --border: 214.3 31.8% 91.4%;
32
- --input: 214.3 31.8% 91.4%;
33
+ --input: 214.3 15.9% 83%;
33
34
  --ring: 222.2 84% 4.9%;
34
35
 
35
36
  --radius: 0.5rem;
@@ -57,11 +58,11 @@
57
58
  --accent: 217.2 32.6% 17.5%;
58
59
  --accent-foreground: 210 40% 98%;
59
60
 
60
- --destructive: 0 62.8% 30.6%;
61
- --destructive-foreground: 210 40% 98%;
61
+ --destructive: 0 90.6% 70.8%;
62
+ --destructive-foreground: 222.2 47.4% 11.2%;
62
63
 
63
64
  --border: 217.2 32.6% 17.5%;
64
- --input: 217.2 32.6% 17.5%;
65
+ --input: 217.2 28.5% 20.4%;
65
66
  --ring: 212.7 26.8% 83.9%;
66
67
  }
67
68
 
@@ -3,6 +3,7 @@
3
3
  import * as React from "react";
4
4
  import { ThemeProvider as NextThemesProvider } from "next-themes";
5
5
  import type { ThemeProviderProps } from "next-themes";
6
+ import { buildThemePaletteCss, type ThemePaletteOptions } from "./lib/theme-palette";
6
7
 
7
8
  export type AppTheme = "light" | "dark";
8
9
 
@@ -12,14 +13,23 @@ export type ArronqzyThemeProviderProps = Omit<
12
13
  > & {
13
14
  defaultTheme?: AppTheme;
14
15
  enableSystem?: boolean;
16
+ /** Contrast-checked theme variables generated with chroma-js; `false` keeps the static CSS. */
17
+ palette?: ThemePaletteOptions | false;
15
18
  };
16
19
 
17
20
  export function ThemeProvider({
18
21
  children,
19
22
  defaultTheme = "dark",
20
23
  enableSystem = true,
24
+ palette = {},
21
25
  ...props
22
26
  }: ArronqzyThemeProviderProps) {
27
+ const paletteKey = palette ? JSON.stringify(palette) : "";
28
+ const paletteCss = React.useMemo(
29
+ () => (paletteKey ? buildThemePaletteCss(JSON.parse(paletteKey) as ThemePaletteOptions) : ""),
30
+ [paletteKey],
31
+ );
32
+
23
33
  return (
24
34
  <NextThemesProvider
25
35
  attribute="class"
@@ -27,10 +37,12 @@ export function ThemeProvider({
27
37
  enableSystem={enableSystem}
28
38
  {...props}
29
39
  >
40
+ {paletteCss ? (
41
+ <style data-arronqzy-theme-palette="" dangerouslySetInnerHTML={{ __html: paletteCss }} />
42
+ ) : null}
30
43
  {children}
31
44
  </NextThemesProvider>
32
45
  );
33
46
  }
34
47
 
35
48
  export { useTheme } from "next-themes";
36
-
@@ -29,15 +29,14 @@ const ResizableHandle = ({
29
29
  }) => (
30
30
  <ResizablePrimitive.PanelResizeHandle
31
31
  className={cn(
32
- // Smaller, subtler handle (closer to shadcn demo)
33
- "relative flex w-px items-center justify-center bg-border/60 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full [&[data-panel-group-direction=vertical]>div]:rotate-90",
32
+ "relative flex w-px items-center justify-center bg-border/40 transition-colors hover:bg-border/80 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full [&[data-panel-group-direction=vertical]>div]:rotate-90",
34
33
  className
35
34
  )}
36
35
  {...props}
37
36
  >
38
37
  {withHandle && (
39
- <div className="z-10 flex h-4 w-2.5 items-center justify-center rounded-sm border border-border/60 bg-background/60 opacity-60 transition-opacity hover:opacity-100">
40
- <GripVertical className="h-2.5 w-2.5 text-foreground/60" />
38
+ <div className="z-10 flex h-5 w-2.5 items-center justify-center rounded-md border border-border/50 bg-background/80 opacity-50 shadow-sm transition-opacity hover:opacity-100">
39
+ <GripVertical className="h-2.5 w-2.5 text-muted-foreground" />
41
40
  </div>
42
41
  )}
43
42
  </ResizablePrimitive.PanelResizeHandle>