@badnpm/ui 0.0.0-stage → 0.0.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/LICENSE +21 -0
- package/README.md +193 -2
- package/dist/cjs/components/button.cjs +41 -0
- package/dist/cjs/components/input.cjs +11 -0
- package/dist/cjs/components/theme-toggle.cjs +19 -0
- package/dist/cjs/hooks/use-hydrated.cjs +15 -0
- package/dist/cjs/index.cjs +30 -0
- package/dist/cjs/lib/preference.cjs +40 -0
- package/dist/cjs/providers/preference-provider.cjs +127 -0
- package/dist/cjs/providers/theme-provider.cjs +181 -0
- package/dist/cjs/types/components/button.d.ts +12 -0
- package/dist/cjs/types/components/index.d.ts +3 -0
- package/dist/cjs/types/components/input.d.ts +5 -0
- package/dist/cjs/types/components/theme-toggle.d.ts +14 -0
- package/dist/cjs/types/hooks/index.d.ts +1 -0
- package/dist/cjs/types/hooks/use-hydrated.d.ts +4 -0
- package/dist/cjs/types/index.d.ts +8 -0
- package/dist/cjs/types/lib/index.d.ts +2 -0
- package/dist/cjs/types/lib/preference.d.ts +85 -0
- package/dist/cjs/types/lib/utils.d.ts +1 -0
- package/dist/cjs/types/package.json +1 -0
- package/dist/cjs/types/providers/index.d.ts +2 -0
- package/dist/cjs/types/providers/preference-provider.d.ts +20 -0
- package/dist/cjs/types/providers/theme-provider.d.ts +25 -0
- package/dist/esm/components/button.js +38 -0
- package/dist/esm/components/input.js +9 -0
- package/dist/esm/components/theme-toggle.js +17 -0
- package/dist/esm/hooks/use-hydrated.js +13 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/lib/preference.js +35 -0
- package/dist/esm/providers/preference-provider.js +124 -0
- package/dist/esm/providers/theme-provider.js +178 -0
- package/dist/styles/body.css +7 -0
- package/dist/styles/font/LICENSE +93 -0
- package/dist/styles/font/files/11944884.woff2 +0 -0
- package/dist/styles/font/files/3f081d19.woff2 +0 -0
- package/dist/styles/font/files/aa4aa22f.woff2 +0 -0
- package/dist/styles/font/files/ba1a9b92.woff2 +0 -0
- package/dist/styles/font/files/eadd8e6c.woff2 +0 -0
- package/dist/styles/font/geist.css +2 -0
- package/dist/styles/index.css +2 -0
- package/dist/types/components/button.d.ts +12 -0
- package/dist/types/components/index.d.ts +3 -0
- package/dist/types/components/input.d.ts +5 -0
- package/dist/types/components/theme-toggle.d.ts +14 -0
- package/dist/types/hooks/index.d.ts +1 -0
- package/dist/types/hooks/use-hydrated.d.ts +4 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/lib/index.d.ts +2 -0
- package/dist/types/lib/preference.d.ts +85 -0
- package/dist/types/lib/utils.d.ts +1 -0
- package/dist/types/providers/index.d.ts +2 -0
- package/dist/types/providers/preference-provider.d.ts +20 -0
- package/dist/types/providers/theme-provider.d.ts +25 -0
- package/dist/umd/index.js +3541 -0
- package/package.json +81 -4
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useState, useCallback, useLayoutEffect, useEffect, useMemo, useContext } from 'react';
|
|
4
|
+
import { useHydrated } from '../hooks/use-hydrated.js';
|
|
5
|
+
|
|
6
|
+
const COLOR_SCHEME_QUERY = '(prefers-color-scheme: dark)';
|
|
7
|
+
const ThemeProviderContext = createContext(undefined);
|
|
8
|
+
// 校验存储值,同时将类型收窄为合法的主题选项。
|
|
9
|
+
function isTheme(value) {
|
|
10
|
+
return value === 'dark' || value === 'light' || value === 'system';
|
|
11
|
+
}
|
|
12
|
+
// 优先读取已保存的主题;值无效或存储不可用时使用默认主题。
|
|
13
|
+
function readTheme(storageKey, fallback) {
|
|
14
|
+
try {
|
|
15
|
+
const value = localStorage.getItem(storageKey);
|
|
16
|
+
return isTheme(value) ? value : fallback;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// 存储不可用时回退到默认主题,不影响组件初始化。
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// 持久化用户选择,存储失败时仍允许更新页面主题。
|
|
24
|
+
function persistTheme(storageKey, theme) {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(storageKey, theme);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// 忽略存储异常,主题仍可在当前页面内切换。
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// 将用户选择解析为实际配色;system 每次读取当前系统偏好。
|
|
33
|
+
function resolveTheme(theme) {
|
|
34
|
+
if (theme !== 'system') {
|
|
35
|
+
return theme;
|
|
36
|
+
}
|
|
37
|
+
return window.matchMedia(COLOR_SCHEME_QUERY).matches ? 'dark' : 'light';
|
|
38
|
+
}
|
|
39
|
+
// 更新根元素的主题类名,并返回清理临时样式及动画帧的函数。
|
|
40
|
+
function applyTheme(theme, disableTransitionOnChange) {
|
|
41
|
+
const root = document.documentElement;
|
|
42
|
+
const resolvedTheme = resolveTheme(theme);
|
|
43
|
+
// 主题已生效且没有冲突类名时,跳过重复的 DOM 操作。
|
|
44
|
+
if (root.classList.contains(resolvedTheme) && !root.classList.contains(resolvedTheme === 'dark' ? 'light' : 'dark')) {
|
|
45
|
+
return () => { };
|
|
46
|
+
}
|
|
47
|
+
const style = disableTransitionOnChange ? document.createElement('style') : null;
|
|
48
|
+
if (style) {
|
|
49
|
+
style.textContent = '*,*::before,*::after{transition:none!important}';
|
|
50
|
+
document.head.appendChild(style);
|
|
51
|
+
// 强制浏览器计算样式,确保禁用过渡的规则先于主题切换生效。
|
|
52
|
+
window.getComputedStyle(root).getPropertyValue('transition-property');
|
|
53
|
+
}
|
|
54
|
+
root.classList.remove('light', 'dark');
|
|
55
|
+
root.classList.add(resolvedTheme);
|
|
56
|
+
if (!style) {
|
|
57
|
+
return () => { };
|
|
58
|
+
}
|
|
59
|
+
// 在禁用过渡期间计算新主题样式,再通过两次动画帧延迟移除规则。
|
|
60
|
+
window.getComputedStyle(root).getPropertyValue('color');
|
|
61
|
+
let frame = requestAnimationFrame(() => {
|
|
62
|
+
frame = requestAnimationFrame(() => style.remove());
|
|
63
|
+
});
|
|
64
|
+
// 重新应用主题或卸载时,取消待执行的帧并移除临时样式。
|
|
65
|
+
return () => {
|
|
66
|
+
cancelAnimationFrame(frame);
|
|
67
|
+
style.remove();
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 判断事件是否来自可编辑区域,避免主题快捷键干扰输入。
|
|
71
|
+
function isEditableTarget(target) {
|
|
72
|
+
if (!(target instanceof HTMLElement)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (target.isContentEditable) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return target.closest('input, textarea, select') !== null;
|
|
79
|
+
}
|
|
80
|
+
// 统一管理主题状态、页面样式及系统和跨标签页同步。
|
|
81
|
+
function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'bad-ui:theme', disableTransitionOnChange = true, keyboardShortcut, }) {
|
|
82
|
+
const hydrated = useHydrated();
|
|
83
|
+
const [storedTheme, setThemeState] = useState(() => readTheme(storageKey, defaultTheme));
|
|
84
|
+
// 水合首帧与服务端一致,完成后才使用浏览器保存的主题。
|
|
85
|
+
const theme = hydrated ? storedTheme : defaultTheme;
|
|
86
|
+
// 所有主动切换共用此入口,统一更新存储和 React 状态。
|
|
87
|
+
const setTheme = useCallback((nextTheme) => {
|
|
88
|
+
persistTheme(storageKey, nextTheme);
|
|
89
|
+
setThemeState(nextTheme);
|
|
90
|
+
}, [storageKey]);
|
|
91
|
+
// 按需解析系统配色,按钮和快捷键共用同一切换逻辑。
|
|
92
|
+
const toggleTheme = useCallback(() => {
|
|
93
|
+
setTheme(resolveTheme(theme) === 'dark' ? 'light' : 'dark');
|
|
94
|
+
}, [theme, setTheme]);
|
|
95
|
+
// 水合完成后应用主题;服务端与水合首帧不访问系统配色或修改根元素。
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
if (!hydrated)
|
|
98
|
+
return;
|
|
99
|
+
let cleanup = applyTheme(theme, disableTransitionOnChange);
|
|
100
|
+
const mediaQuery = window.matchMedia(COLOR_SCHEME_QUERY);
|
|
101
|
+
const handleChange = () => {
|
|
102
|
+
cleanup();
|
|
103
|
+
cleanup = applyTheme(theme, disableTransitionOnChange);
|
|
104
|
+
};
|
|
105
|
+
// 仅跟随系统时订阅配色变化;显式选择深浅色后不再跟随。
|
|
106
|
+
if (theme === 'system') {
|
|
107
|
+
mediaQuery.addEventListener('change', handleChange);
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
mediaQuery.removeEventListener('change', handleChange);
|
|
111
|
+
cleanup();
|
|
112
|
+
};
|
|
113
|
+
}, [theme, disableTransitionOnChange, hydrated]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!hydrated || !keyboardShortcut)
|
|
116
|
+
return;
|
|
117
|
+
const shortcutKey = keyboardShortcut.toLowerCase();
|
|
118
|
+
// 忽略已处理事件、输入法组合和长按重复。
|
|
119
|
+
const handleKeyDown = (event) => {
|
|
120
|
+
if (event.defaultPrevented || event.isComposing || event.repeat) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (isEditableTarget(event.target)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (event.key.toLowerCase() !== shortcutKey) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
toggleTheme();
|
|
133
|
+
};
|
|
134
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
135
|
+
return () => {
|
|
136
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
137
|
+
};
|
|
138
|
+
}, [toggleTheme, hydrated, keyboardShortcut]);
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
// storage 事件用于接收同源其他标签页的主题变更。
|
|
141
|
+
const handleStorageChange = (event) => {
|
|
142
|
+
if (event.storageArea !== localStorage) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// clear() 触发的事件 key 为 null,也需要回退到默认主题。
|
|
146
|
+
if (event.key !== null && event.key !== storageKey) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (isTheme(event.newValue)) {
|
|
150
|
+
setThemeState(event.newValue);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// 存储项被移除、清空或写入无效值时,恢复默认主题。
|
|
154
|
+
setThemeState(defaultTheme);
|
|
155
|
+
};
|
|
156
|
+
window.addEventListener('storage', handleStorageChange);
|
|
157
|
+
return () => {
|
|
158
|
+
window.removeEventListener('storage', handleStorageChange);
|
|
159
|
+
};
|
|
160
|
+
}, [defaultTheme, storageKey]);
|
|
161
|
+
// 保持上下文值的引用稳定,避免父组件重渲染引起无关更新。
|
|
162
|
+
const value = useMemo(() => ({
|
|
163
|
+
theme,
|
|
164
|
+
setTheme,
|
|
165
|
+
toggleTheme,
|
|
166
|
+
}), [theme, setTheme, toggleTheme]);
|
|
167
|
+
return jsx(ThemeProviderContext.Provider, { value: value, children: children });
|
|
168
|
+
}
|
|
169
|
+
// 获取主题上下文,并在缺少 Provider 时给出明确错误。
|
|
170
|
+
const useTheme = () => {
|
|
171
|
+
const context = useContext(ThemeProviderContext);
|
|
172
|
+
if (context === undefined) {
|
|
173
|
+
throw new Error('useTheme must be used within a ThemeProvider');
|
|
174
|
+
}
|
|
175
|
+
return context;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export { ThemeProvider, useTheme };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font) Geist-Italic[wght].ttf: Copyright 2024 The Geist Project Authors (https://github.com/vercel/geist-font)
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
-----------------------------------------------------------
|
|
9
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
10
|
+
-----------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
PREAMBLE
|
|
13
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
14
|
+
development of collaborative font projects, to support the font creation
|
|
15
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
16
|
+
open framework in which fonts may be shared and improved in partnership
|
|
17
|
+
with others.
|
|
18
|
+
|
|
19
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
20
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
21
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
22
|
+
redistributed and/or sold with any software provided that any reserved
|
|
23
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
24
|
+
however, cannot be released under any other type of license. The
|
|
25
|
+
requirement for fonts to remain under this license does not apply
|
|
26
|
+
to any document created using the fonts or their derivatives.
|
|
27
|
+
|
|
28
|
+
DEFINITIONS
|
|
29
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
30
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
31
|
+
include source files, build scripts and documentation.
|
|
32
|
+
|
|
33
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
34
|
+
copyright statement(s).
|
|
35
|
+
|
|
36
|
+
"Original Version" refers to the collection of Font Software components as
|
|
37
|
+
distributed by the Copyright Holder(s).
|
|
38
|
+
|
|
39
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
40
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
41
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
42
|
+
new environment.
|
|
43
|
+
|
|
44
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
45
|
+
writer or other person who contributed to the Font Software.
|
|
46
|
+
|
|
47
|
+
PERMISSION & CONDITIONS
|
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
49
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
50
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
51
|
+
Software, subject to the following conditions:
|
|
52
|
+
|
|
53
|
+
1) Neither the Font Software nor any of its individual components,
|
|
54
|
+
in Original or Modified Versions, may be sold by itself.
|
|
55
|
+
|
|
56
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
57
|
+
redistributed and/or sold with any software, provided that each copy
|
|
58
|
+
contains the above copyright notice and this license. These can be
|
|
59
|
+
included either as stand-alone text files, human-readable headers or
|
|
60
|
+
in the appropriate machine-readable metadata fields within text or
|
|
61
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
62
|
+
|
|
63
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
64
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
65
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
66
|
+
presented to the users.
|
|
67
|
+
|
|
68
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
69
|
+
Software shall not be used to promote, endorse or advertise any
|
|
70
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
71
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
72
|
+
permission.
|
|
73
|
+
|
|
74
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
75
|
+
must be distributed entirely under this license, and must not be
|
|
76
|
+
distributed under any other license. The requirement for fonts to
|
|
77
|
+
remain under this license does not apply to any document created
|
|
78
|
+
using the Font Software.
|
|
79
|
+
|
|
80
|
+
TERMINATION
|
|
81
|
+
This license becomes null and void if any of the above conditions are
|
|
82
|
+
not met.
|
|
83
|
+
|
|
84
|
+
DISCLAIMER
|
|
85
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
86
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
87
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
88
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
89
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
90
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
91
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
92
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
93
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(files/ba1a9b92.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(files/aa4aa22f.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(files/11944884.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(files/eadd8e6c.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Geist Variable;font-style:normal;font-display:swap;font-weight:100 900;src:url(files/3f081d19.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@layer theme{:root{--font-sans:"Geist Variable", sans-serif;--default-font-family:var(--font-sans)}}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:block))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){:root,:host{--shimmer-angle:20deg}*,:before,:after,::backdrop{--tw-border-style:solid;--tw-font-weight:initial;--tw-outline-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--scroll-fade-t:0px;--scroll-fade-b:0px;--scroll-fade-s:0px;--scroll-fade-e:0px;--scroll-fade-mask:initial;--shimmer-image:initial;--shimmer-text-fill:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-medium:500;--radius-md:calc(var(--radius) * .8);--ease-out:cubic-bezier(0, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}*{border-color:var(--border);outline-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){*{outline-color:color-mix(in oklab, var(--ring) 50%, transparent)}}}@layer components;@layer utilities{.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hidden{display:none}.inline-flex{display:inline-flex}.size-6{width:calc(var(--spacing) * 6);height:calc(var(--spacing) * 6)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-8{width:calc(var(--spacing) * 8);height:calc(var(--spacing) * 8)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.w-full{width:100%}.min-w-0{min-width:0}.shrink-0{flex-shrink:0}.animate-in{animation:enter var(--tw-animation-duration,var(--tw-duration,.15s))var(--tw-ease,ease)var(--tw-animation-delay,0s)var(--tw-animation-iteration-count,1)var(--tw-animation-direction,normal)var(--tw-animation-fill-mode,none)}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.rounded-\[min\(var\(--radius-md\)\,10px\)\]{border-radius:min(var(--radius-md), 10px)}.rounded-\[min\(var\(--radius-md\)\,12px\)\]{border-radius:min(var(--radius-md), 12px)}.rounded-lg{border-radius:var(--radius)}.border{border-style:var(--tw-border-style);border-width:1px}.border-border{border-color:var(--border)}.border-input{border-color:var(--input)}.border-transparent{border-color:#0000}.bg-background{background-color:var(--background)}.bg-destructive\/10{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.bg-destructive\/10{background-color:color-mix(in oklab, var(--destructive) 10%, transparent)}}.bg-primary{background-color:var(--primary)}.bg-secondary{background-color:var(--secondary)}.bg-transparent{background-color:#0000}.bg-clip-padding{background-clip:padding-box}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.py-1{padding-block:var(--spacing)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.8rem\]{font-size:.8rem}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.whitespace-nowrap{white-space:nowrap}.text-destructive{color:var(--destructive)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-secondary-foreground{color:var(--secondary-foreground)}.underline-offset-4{text-underline-offset:4px}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.zoom-in-75{--tw-enter-scale:.75}.fade-in{--tw-enter-opacity:0}.spin-in-90{--tw-enter-rotate:calc(90*1deg)}.file\:inline-flex::file-selector-button{display:inline-flex}.file\:h-6::file-selector-button{height:calc(var(--spacing) * 6)}.file\:border-0::file-selector-button{border-style:var(--tw-border-style);border-width:0}.file\:bg-transparent::file-selector-button{background-color:#0000}.file\:text-sm::file-selector-button{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.file\:font-medium::file-selector-button{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.file\:text-foreground::file-selector-button{color:var(--foreground)}.placeholder\:text-muted-foreground::placeholder{color:var(--muted-foreground)}@media (hover:hover){.hover\:bg-\[color-mix\(in_oklch\,var\(--secondary\)\,var\(--foreground\)_5\%\)\]:hover{background-color:var(--secondary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-\[color-mix\(in_oklch\,var\(--secondary\)\,var\(--foreground\)_5\%\)\]:hover{background-color:color-mix(in oklch,var(--secondary),var(--foreground) 5%)}}.hover\:bg-destructive\/20:hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-destructive\/20:hover{background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.hover\:bg-muted:hover{background-color:var(--muted)}.hover\:bg-primary\/80:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/80:hover{background-color:color-mix(in oklab, var(--primary) 80%, transparent)}}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:underline:hover{text-decoration-line:underline}}.focus-visible\:border-destructive\/40:focus-visible{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:border-destructive\/40:focus-visible{border-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.focus-visible\:border-ring:focus-visible{border-color:var(--ring)}.focus-visible\:ring-3:focus-visible{--tw-ring-shadow:var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-destructive\/20:focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.active\:not-aria-\[haspopup\]\:translate-y-px:active:not([aria-haspopup]){--tw-translate-y:1px;translate:var(--tw-translate-x) var(--tw-translate-y)}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:bg-input\/50:disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.disabled\:bg-input\/50:disabled{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.disabled\:opacity-50:disabled{opacity:.5}:where([data-slot=button-group]) .in-data-\[slot\=button-group\]\:rounded-lg{border-radius:var(--radius)}.has-data-\[icon\=inline-end\]\:pr-1\.5:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-end\]\:pr-2:has([data-icon=inline-end]){padding-right:calc(var(--spacing) * 2)}.has-data-\[icon\=inline-start\]\:pl-1\.5:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 1.5)}.has-data-\[icon\=inline-start\]\:pl-2:has([data-icon=inline-start]){padding-left:calc(var(--spacing) * 2)}.aria-expanded\:bg-muted[aria-expanded=true]{background-color:var(--muted)}.aria-expanded\:bg-secondary[aria-expanded=true]{background-color:var(--secondary)}.aria-expanded\:text-foreground[aria-expanded=true]{color:var(--foreground)}.aria-expanded\:text-secondary-foreground[aria-expanded=true]{color:var(--secondary-foreground)}.aria-invalid\:border-destructive[aria-invalid=true]{border-color:var(--destructive)}.aria-invalid\:ring-3[aria-invalid=true]{--tw-ring-shadow:var(--tw-ring-inset, ) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.aria-invalid\:ring-destructive\/20[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}@media (prefers-reduced-motion:reduce){.motion-reduce\:animate-none{animation:none}}@media (min-width:48rem){.md\:text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}}.dark\:block:is(.dark *){display:block}.dark\:hidden:is(.dark *){display:none}.dark\:border-input:is(.dark *){border-color:var(--input)}.dark\:bg-destructive\/20:is(.dark *){background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-destructive\/20:is(.dark *){background-color:color-mix(in oklab, var(--destructive) 20%, transparent)}}.dark\:bg-input\/30:is(.dark *){background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:bg-input\/30:is(.dark *){background-color:color-mix(in oklab, var(--input) 30%, transparent)}}@media (hover:hover){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-destructive\/30:is(.dark *):hover{background-color:color-mix(in oklab, var(--destructive) 30%, transparent)}}.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-input\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--input) 50%, transparent)}}.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.dark\:hover\:bg-muted\/50:is(.dark *):hover{background-color:color-mix(in oklab, var(--muted) 50%, transparent)}}}.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:focus-visible\:ring-destructive\/40:is(.dark *):focus-visible{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.dark\:disabled\:bg-input\/80:is(.dark *):disabled{background-color:var(--input)}@supports (color:color-mix(in lab, red, red)){.dark\:disabled\:bg-input\/80:is(.dark *):disabled{background-color:color-mix(in oklab, var(--input) 80%, transparent)}}.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:border-destructive\/50:is(.dark *)[aria-invalid=true]{border-color:color-mix(in oklab, var(--destructive) 50%, transparent)}}.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:var(--destructive)}@supports (color:color-mix(in lab, red, red)){.dark\:aria-invalid\:ring-destructive\/40:is(.dark *)[aria-invalid=true]{--tw-ring-color:color-mix(in oklab, var(--destructive) 40%, transparent)}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3 svg:not([class*=size-]){width:calc(var(--spacing) * 3);height:calc(var(--spacing) * 3)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-3\.5 svg:not([class*=size-]){width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}}@property --tw-animation-delay{syntax:"*";inherits:false;initial-value:0s}@property --tw-animation-direction{syntax:"*";inherits:false;initial-value:normal}@property --tw-animation-duration{syntax:"*";inherits:false}@property --tw-animation-fill-mode{syntax:"*";inherits:false;initial-value:none}@property --tw-animation-iteration-count{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-enter-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-enter-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-blur{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-opacity{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-rotate{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-scale{syntax:"*";inherits:false;initial-value:1}@property --tw-exit-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-exit-translate-y{syntax:"*";inherits:false;initial-value:0}@property --scroll-fade-t{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-b{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-s{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-e{syntax:"<length-percentage>";inherits:false;initial-value:0}@property --scroll-fade-mask{syntax:"*";inherits:false}@property --shimmer-angle{syntax:"<angle>";inherits:true;initial-value:20deg}@property --shimmer-image{syntax:"*";inherits:false}@property --shimmer-text-fill{syntax:"*";inherits:false}@media (prefers-reduced-motion:reduce){.shimmer{-webkit-text-fill-color:currentColor;background-image:none;animation:none}}:root,:root[data-theme=neutral]{--destructive:oklch(57.7% .245 27.325);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--radius:.625rem;--primary:oklch(20.5% 0 0);--primary-foreground:oklch(98.5% 0 0);--ring:oklch(70.8% 0 0);--sidebar-primary:oklch(20.5% 0 0);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-ring:oklch(70.8% 0 0)}:root.dark,:root[data-theme=neutral].dark{--destructive:oklch(70.4% .191 22.216);--chart-1:oklch(87% 0 0);--chart-2:oklch(55.6% 0 0);--chart-3:oklch(43.9% 0 0);--chart-4:oklch(37.1% 0 0);--chart-5:oklch(26.9% 0 0);--primary:oklch(92.2% 0 0);--primary-foreground:oklch(20.5% 0 0);--ring:oklch(55.6% 0 0);--sidebar-primary:oklch(48.8% .243 264.376);--sidebar-primary-foreground:oklch(98.5% 0 0);--sidebar-ring:oklch(55.6% 0 0)}:root[data-theme=blue]{--primary:oklch(48.8% .243 264.376);--primary-foreground:oklch(97% .014 254.604);--sidebar-primary:oklch(54.6% .245 262.881);--sidebar-primary-foreground:oklch(97% .014 254.604)}:root[data-theme=blue].dark{--primary:oklch(42.4% .199 265.638);--primary-foreground:oklch(97% .014 254.604);--sidebar-primary:oklch(62.3% .214 259.815);--sidebar-primary-foreground:oklch(97% .014 254.604)}:root[data-theme=green]{--primary:oklch(52.7% .154 150.069);--primary-foreground:oklch(98.2% .018 155.826);--sidebar-primary:oklch(62.7% .194 149.214);--sidebar-primary-foreground:oklch(98.2% .018 155.826)}:root[data-theme=green].dark{--primary:oklch(44.8% .119 151.328);--primary-foreground:oklch(98.2% .018 155.826);--sidebar-primary:oklch(72.3% .219 149.579);--sidebar-primary-foreground:oklch(98.2% .018 155.826)}:root[data-theme=orange]{--primary:oklch(55.3% .195 38.402);--primary-foreground:oklch(98% .016 73.684);--sidebar-primary:oklch(64.6% .222 41.116);--sidebar-primary-foreground:oklch(98% .016 73.684)}:root[data-theme=orange].dark{--primary:oklch(47% .157 37.304);--primary-foreground:oklch(98% .016 73.684);--sidebar-primary:oklch(70.5% .213 47.604);--sidebar-primary-foreground:oklch(98% .016 73.684)}:root[data-theme=pink]{--primary:oklch(52.5% .223 3.958);--primary-foreground:oklch(97.1% .014 343.198);--sidebar-primary:oklch(59.2% .249 .584);--sidebar-primary-foreground:oklch(97.1% .014 343.198)}:root[data-theme=pink].dark{--primary:oklch(45.9% .187 3.815);--primary-foreground:oklch(97.1% .014 343.198);--sidebar-primary:oklch(65.6% .241 354.308);--sidebar-primary-foreground:oklch(97.1% .014 343.198)}:root[data-theme=purple]{--primary:oklch(49.6% .265 301.924);--primary-foreground:oklch(97.7% .014 308.299);--sidebar-primary:oklch(55.8% .288 302.321);--sidebar-primary-foreground:oklch(97.7% .014 308.299)}:root[data-theme=purple].dark{--primary:oklch(43.8% .218 303.724);--primary-foreground:oklch(97.7% .014 308.299);--sidebar-primary:oklch(62.7% .265 303.9);--sidebar-primary-foreground:oklch(97.7% .014 308.299)}:root[data-theme=red]{--primary:oklch(50.5% .213 27.518);--primary-foreground:oklch(97.1% .013 17.38);--sidebar-primary:oklch(57.7% .245 27.325);--sidebar-primary-foreground:oklch(97.1% .013 17.38)}:root[data-theme=red].dark{--primary:oklch(44.4% .177 26.899);--primary-foreground:oklch(97.1% .013 17.38);--sidebar-primary:oklch(63.7% .237 25.331);--sidebar-primary-foreground:oklch(97.1% .013 17.38)}:root[data-theme=rose]{--primary:oklch(51.4% .222 16.935);--primary-foreground:oklch(96.9% .015 12.422);--sidebar-primary:oklch(58.6% .253 17.585);--sidebar-primary-foreground:oklch(96.9% .015 12.422)}:root[data-theme=rose].dark{--primary:oklch(45.5% .188 13.697);--primary-foreground:oklch(96.9% .015 12.422);--sidebar-primary:oklch(64.5% .246 16.439);--sidebar-primary-foreground:oklch(96.9% .015 12.422)}:root[data-theme=sky]{--primary:oklch(50% .134 242.749);--primary-foreground:oklch(97.7% .013 236.62);--sidebar-primary:oklch(58.8% .158 241.966);--sidebar-primary-foreground:oklch(97.7% .013 236.62)}:root[data-theme=sky].dark{--primary:oklch(44.3% .11 240.79);--primary-foreground:oklch(97.7% .013 236.62);--sidebar-primary:oklch(68.5% .169 237.323);--sidebar-primary-foreground:oklch(29.3% .066 243.157)}:root[data-theme=yellow]{--primary:oklch(85.2% .199 91.936);--primary-foreground:oklch(42.1% .095 57.708);--sidebar-primary:oklch(68.1% .162 75.834);--sidebar-primary-foreground:oklch(98.7% .026 102.212)}:root[data-theme=yellow].dark{--primary:oklch(79.5% .184 86.047);--primary-foreground:oklch(42.1% .095 57.708);--sidebar-primary:oklch(79.5% .184 86.047);--sidebar-primary-foreground:oklch(98.7% .026 102.212)}:root,:root[data-base-color=neutral]{--background:oklch(100% 0 0);--foreground:oklch(14.5% 0 0);--card:oklch(100% 0 0);--card-foreground:oklch(14.5% 0 0);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.5% 0 0);--secondary:oklch(97% 0 0);--secondary-foreground:oklch(20.5% 0 0);--muted:oklch(97% 0 0);--muted-foreground:oklch(55.6% 0 0);--accent:oklch(97% 0 0);--accent-foreground:oklch(20.5% 0 0);--border:oklch(92.2% 0 0);--input:oklch(92.2% 0 0);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.5% 0 0);--sidebar-accent:oklch(97% 0 0);--sidebar-accent-foreground:oklch(20.5% 0 0);--sidebar-border:oklch(92.2% 0 0)}:root.dark,:root[data-base-color=neutral].dark{--background:oklch(14.5% 0 0);--foreground:oklch(98.5% 0 0);--card:oklch(20.5% 0 0);--card-foreground:oklch(98.5% 0 0);--popover:oklch(20.5% 0 0);--popover-foreground:oklch(98.5% 0 0);--secondary:oklch(26.9% 0 0);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(26.9% 0 0);--muted-foreground:oklch(70.8% 0 0);--accent:oklch(26.9% 0 0);--accent-foreground:oklch(98.5% 0 0);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--sidebar:oklch(20.5% 0 0);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(26.9% 0 0);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1)}:root[data-base-color=mist]{--background:oklch(100% 0 0);--foreground:oklch(14.8% .004 228.8);--card:oklch(100% 0 0);--card-foreground:oklch(14.8% .004 228.8);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.8% .004 228.8);--secondary:oklch(96.3% .002 197.1);--secondary-foreground:oklch(21.8% .008 223.9);--muted:oklch(96.3% .002 197.1);--muted-foreground:oklch(56% .021 213.5);--accent:oklch(96.3% .002 197.1);--accent-foreground:oklch(21.8% .008 223.9);--border:oklch(92.5% .005 214.3);--input:oklch(92.5% .005 214.3);--sidebar:oklch(98.7% .002 197.1);--sidebar-foreground:oklch(14.8% .004 228.8);--sidebar-accent:oklch(96.3% .002 197.1);--sidebar-accent-foreground:oklch(21.8% .008 223.9);--sidebar-border:oklch(92.5% .005 214.3)}:root[data-base-color=mist].dark{--background:oklch(14.8% .004 228.8);--foreground:oklch(98.7% .002 197.1);--card:oklch(21.8% .008 223.9);--card-foreground:oklch(98.7% .002 197.1);--popover:oklch(21.8% .008 223.9);--popover-foreground:oklch(98.7% .002 197.1);--secondary:oklch(27.5% .011 216.9);--secondary-foreground:oklch(98.7% .002 197.1);--muted:oklch(27.5% .011 216.9);--muted-foreground:oklch(72.3% .014 214.4);--accent:oklch(27.5% .011 216.9);--accent-foreground:oklch(98.7% .002 197.1);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--sidebar:oklch(21.8% .008 223.9);--sidebar-foreground:oklch(98.7% .002 197.1);--sidebar-accent:oklch(27.5% .011 216.9);--sidebar-accent-foreground:oklch(98.7% .002 197.1);--sidebar-border:oklch(100% 0 0/.1)}:root[data-base-color=zinc]{--background:oklch(100% 0 0);--foreground:oklch(14.1% .005 285.823);--card:oklch(100% 0 0);--card-foreground:oklch(14.1% .005 285.823);--popover:oklch(100% 0 0);--popover-foreground:oklch(14.1% .005 285.823);--secondary:oklch(96.7% .001 286.375);--secondary-foreground:oklch(21% .006 285.885);--muted:oklch(96.7% .001 286.375);--muted-foreground:oklch(55.2% .016 285.938);--accent:oklch(96.7% .001 286.375);--accent-foreground:oklch(21% .006 285.885);--border:oklch(92% .004 286.32);--input:oklch(92% .004 286.32);--sidebar:oklch(98.5% 0 0);--sidebar-foreground:oklch(14.1% .005 285.823);--sidebar-accent:oklch(96.7% .001 286.375);--sidebar-accent-foreground:oklch(21% .006 285.885);--sidebar-border:oklch(92% .004 286.32)}:root[data-base-color=zinc].dark{--background:oklch(14.1% .005 285.823);--foreground:oklch(98.5% 0 0);--card:oklch(21% .006 285.885);--card-foreground:oklch(98.5% 0 0);--popover:oklch(21% .006 285.885);--popover-foreground:oklch(98.5% 0 0);--secondary:oklch(27.4% .006 286.033);--secondary-foreground:oklch(98.5% 0 0);--muted:oklch(27.4% .006 286.033);--muted-foreground:oklch(70.5% .015 286.067);--accent:oklch(27.4% .006 286.033);--accent-foreground:oklch(98.5% 0 0);--border:oklch(100% 0 0/.1);--input:oklch(100% 0 0/.15);--sidebar:oklch(21% .006 285.885);--sidebar-foreground:oklch(98.5% 0 0);--sidebar-accent:oklch(27.4% .006 286.033);--sidebar-accent-foreground:oklch(98.5% 0 0);--sidebar-border:oklch(100% 0 0/.1)}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0)scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1))rotate(var(--tw-enter-rotate,0));filter:blur(var(--tw-enter-blur,0))}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
3
|
+
import { Button as Button$1 } from '@base-ui/react/button';
|
|
4
|
+
import { VariantProps } from 'class-variance-authority';
|
|
5
|
+
|
|
6
|
+
declare const buttonVariants: (props?: ({
|
|
7
|
+
variant?: "default" | "outline" | "secondary" | "ghost" | "destructive" | "link" | null | undefined;
|
|
8
|
+
size?: "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | null | undefined;
|
|
9
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
10
|
+
declare function Button({ className, variant, size, ...props }: Button$1.Props & VariantProps<typeof buttonVariants>): react.JSX.Element;
|
|
11
|
+
|
|
12
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import { Button } from './button.js';
|
|
4
|
+
|
|
5
|
+
type ThemeToggleProps = Omit<ComponentProps<typeof Button>, 'children'> & {
|
|
6
|
+
/** 切换到深色模式的无障碍文案,默认为“切换到深色主题”。 */
|
|
7
|
+
darkLabel?: string;
|
|
8
|
+
/** 切换到浅色模式的无障碍文案,默认为“切换到浅色主题”。 */
|
|
9
|
+
lightLabel?: string;
|
|
10
|
+
};
|
|
11
|
+
declare function ThemeToggle({ darkLabel, lightLabel, variant, size, title, onClick, ...props }: ThemeToggleProps): react.JSX.Element;
|
|
12
|
+
|
|
13
|
+
export { ThemeToggle };
|
|
14
|
+
export type { ThemeToggleProps };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useHydrated } from './use-hydrated.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { Button, buttonVariants } from './components/button.js';
|
|
2
|
+
export { Input } from './components/input.js';
|
|
3
|
+
export { ThemeToggle, ThemeToggleProps } from './components/theme-toggle.js';
|
|
4
|
+
export { PreferenceProvider, usePreference } from './providers/preference-provider.js';
|
|
5
|
+
export { ThemeProvider, useTheme } from './providers/theme-provider.js';
|
|
6
|
+
export { useHydrated } from './hooks/use-hydrated.js';
|
|
7
|
+
export { BASE_COLORS, BaseColor, COLOR_THEMES, ColorTheme, DEFAULT_PREFERENCE, Preference, RADIUS_OPTIONS } from './lib/preference.js';
|
|
8
|
+
export { cn } from 'cn';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
declare const BASE_COLORS: readonly [{
|
|
2
|
+
readonly value: "neutral";
|
|
3
|
+
readonly label: "Neutral";
|
|
4
|
+
readonly color: "oklch(0.556 0 0)";
|
|
5
|
+
}, {
|
|
6
|
+
readonly value: "mist";
|
|
7
|
+
readonly label: "Mist";
|
|
8
|
+
readonly color: "oklch(0.56 0.021 213.5)";
|
|
9
|
+
}, {
|
|
10
|
+
readonly value: "zinc";
|
|
11
|
+
readonly label: "Zinc";
|
|
12
|
+
readonly color: "oklch(0.552 0.016 285.938)";
|
|
13
|
+
}];
|
|
14
|
+
declare const COLOR_THEMES: readonly [{
|
|
15
|
+
readonly value: "neutral";
|
|
16
|
+
readonly label: "Neutral";
|
|
17
|
+
readonly color: "oklch(0.205 0 0)";
|
|
18
|
+
}, {
|
|
19
|
+
readonly value: "blue";
|
|
20
|
+
readonly label: "Blue";
|
|
21
|
+
readonly color: "oklch(0.488 0.243 264.376)";
|
|
22
|
+
}, {
|
|
23
|
+
readonly value: "green";
|
|
24
|
+
readonly label: "Green";
|
|
25
|
+
readonly color: "oklch(0.527 0.154 150.069)";
|
|
26
|
+
}, {
|
|
27
|
+
readonly value: "orange";
|
|
28
|
+
readonly label: "Orange";
|
|
29
|
+
readonly color: "oklch(0.553 0.195 38.402)";
|
|
30
|
+
}, {
|
|
31
|
+
readonly value: "pink";
|
|
32
|
+
readonly label: "Pink";
|
|
33
|
+
readonly color: "oklch(0.525 0.223 3.958)";
|
|
34
|
+
}, {
|
|
35
|
+
readonly value: "purple";
|
|
36
|
+
readonly label: "Purple";
|
|
37
|
+
readonly color: "oklch(0.496 0.265 301.924)";
|
|
38
|
+
}, {
|
|
39
|
+
readonly value: "red";
|
|
40
|
+
readonly label: "Red";
|
|
41
|
+
readonly color: "oklch(0.505 0.213 27.518)";
|
|
42
|
+
}, {
|
|
43
|
+
readonly value: "rose";
|
|
44
|
+
readonly label: "Rose";
|
|
45
|
+
readonly color: "oklch(0.514 0.222 16.935)";
|
|
46
|
+
}, {
|
|
47
|
+
readonly value: "sky";
|
|
48
|
+
readonly label: "Sky";
|
|
49
|
+
readonly color: "oklch(0.5 0.134 242.749)";
|
|
50
|
+
}, {
|
|
51
|
+
readonly value: "yellow";
|
|
52
|
+
readonly label: "Yellow";
|
|
53
|
+
readonly color: "oklch(0.852 0.199 91.936)";
|
|
54
|
+
}];
|
|
55
|
+
declare const RADIUS_OPTIONS: readonly [{
|
|
56
|
+
readonly value: 0;
|
|
57
|
+
readonly label: "0";
|
|
58
|
+
}, {
|
|
59
|
+
readonly value: 0.25;
|
|
60
|
+
readonly label: "0.25";
|
|
61
|
+
}, {
|
|
62
|
+
readonly value: 0.5;
|
|
63
|
+
readonly label: "0.5";
|
|
64
|
+
}, {
|
|
65
|
+
readonly value: 0.625;
|
|
66
|
+
readonly label: "Default";
|
|
67
|
+
}, {
|
|
68
|
+
readonly value: 0.75;
|
|
69
|
+
readonly label: "0.75";
|
|
70
|
+
}, {
|
|
71
|
+
readonly value: 1;
|
|
72
|
+
readonly label: "1";
|
|
73
|
+
}];
|
|
74
|
+
type BaseColor = (typeof BASE_COLORS)[number]['value'];
|
|
75
|
+
type ColorTheme = (typeof COLOR_THEMES)[number]['value'];
|
|
76
|
+
type Preference = {
|
|
77
|
+
baseColor: BaseColor;
|
|
78
|
+
theme: ColorTheme;
|
|
79
|
+
/** 根字号单位 rem;0 表示直角,默认 0.625。 */
|
|
80
|
+
radius: number;
|
|
81
|
+
};
|
|
82
|
+
declare const DEFAULT_PREFERENCE: Preference;
|
|
83
|
+
|
|
84
|
+
export { BASE_COLORS, COLOR_THEMES, DEFAULT_PREFERENCE, RADIUS_OPTIONS };
|
|
85
|
+
export type { BaseColor, ColorTheme, Preference };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { cn } from 'cn';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Preference, BaseColor, ColorTheme } from '../lib/preference.js';
|
|
4
|
+
|
|
5
|
+
type PreferenceProviderProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** 可只覆盖部分默认值;已有的有效存储值优先。 */
|
|
8
|
+
defaultPreference?: Partial<Preference>;
|
|
9
|
+
/** 三项偏好共用的 localStorage 键,默认为 'bad-ui:preference'。 */
|
|
10
|
+
storageKey?: string;
|
|
11
|
+
};
|
|
12
|
+
type PreferenceContextValue = Preference & {
|
|
13
|
+
setBaseColor: (baseColor: BaseColor) => void;
|
|
14
|
+
setTheme: (theme: ColorTheme) => void;
|
|
15
|
+
setRadius: (radius: number) => void;
|
|
16
|
+
};
|
|
17
|
+
declare function PreferenceProvider({ children, defaultPreference, storageKey, }: PreferenceProviderProps): react.JSX.Element;
|
|
18
|
+
declare function usePreference(): PreferenceContextValue;
|
|
19
|
+
|
|
20
|
+
export { PreferenceProvider, usePreference };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type Theme = 'dark' | 'light' | 'system';
|
|
5
|
+
type ThemeProviderProps = {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/** 没有有效的已保存主题时使用的模式,默认为 'system'(跟随系统配色)。 */
|
|
8
|
+
defaultTheme?: Theme;
|
|
9
|
+
/** 在 localStorage 中保存主题选择的键名,默认为 'bad-ui:theme'。 */
|
|
10
|
+
storageKey?: string;
|
|
11
|
+
/** 切换主题时是否短暂禁用全页 CSS transition,让新配色立即生效。默认为 true。 */
|
|
12
|
+
disableTransitionOnChange?: boolean;
|
|
13
|
+
/** 切换深浅主题的全局单键快捷键,使用 KeyboardEvent.key(如 'd' 或 'F8'),忽略大小写。省略时关闭。 */
|
|
14
|
+
keyboardShortcut?: string;
|
|
15
|
+
};
|
|
16
|
+
type ThemeProviderState = {
|
|
17
|
+
theme: Theme;
|
|
18
|
+
setTheme: (theme: Theme) => void;
|
|
19
|
+
/** 根据当前实际配色切换深浅模式,切换后停止跟随系统。 */
|
|
20
|
+
toggleTheme: () => void;
|
|
21
|
+
};
|
|
22
|
+
declare function ThemeProvider({ children, defaultTheme, storageKey, disableTransitionOnChange, keyboardShortcut, }: ThemeProviderProps): react.JSX.Element;
|
|
23
|
+
declare const useTheme: () => ThemeProviderState;
|
|
24
|
+
|
|
25
|
+
export { ThemeProvider, useTheme };
|