@docubook/flame 1.0.0-beta.70 → 1.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.
@@ -1,358 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useRef, useState, type ReactNode } from "react";
4
- import { cn } from "../../node/utils";
5
-
6
- export type ThemeName =
7
- | "light"
8
- | "dark"
9
- | "cupcake"
10
- | "bumblebee"
11
- | "emerald"
12
- | "corporate"
13
- | "synthwave"
14
- | "retro"
15
- | "cyberpunk"
16
- | "valentine"
17
- | "halloween"
18
- | "garden"
19
- | "forest"
20
- | "aqua"
21
- | "lofi"
22
- | "pastel"
23
- | "fantasy"
24
- | "wireframe"
25
- | "black"
26
- | "luxury"
27
- | "dracula"
28
- | "cmyk"
29
- | "autumn"
30
- | "business"
31
- | "acid"
32
- | "lemonade"
33
- | "night"
34
- | "coffee"
35
- | "winter"
36
- | "dim"
37
- | "nord"
38
- | "sunset"
39
- | "razzle"
40
- | "sizzle";
41
-
42
- export interface ThemeOption {
43
- value: ThemeName;
44
- label: string;
45
- }
46
-
47
- export const DEFAULT_THEMES: ThemeOption[] = [
48
- { value: "light", label: "Light" },
49
- { value: "dark", label: "Dark" },
50
- ];
51
-
52
- /** Hook to get/set current theme */
53
- export function useTheme() {
54
- const [theme, setThemeState] = useState<ThemeName>(() => {
55
- if (typeof window === "undefined") return "light";
56
- return (localStorage.getItem("theme") as ThemeName) || "light";
57
- });
58
-
59
- useApplyThemeOnMount(theme);
60
-
61
- const setTheme = (newTheme: ThemeName) => {
62
- setThemeState(newTheme);
63
- applyTheme(newTheme);
64
- localStorage.setItem("theme", newTheme);
65
- };
66
-
67
- return { theme, setTheme };
68
- }
69
-
70
- function applyTheme(theme: ThemeName) {
71
- if (typeof document !== "undefined") {
72
- document.documentElement.classList.toggle("dark", theme === "dark");
73
- }
74
- }
75
-
76
- function useApplyThemeOnMount(theme: ThemeName) {
77
- const applied = useRef(false);
78
- useEffect(() => {
79
- if (!applied.current) {
80
- applyTheme(theme);
81
- applied.current = true;
82
- }
83
- }, [theme]);
84
- }
85
-
86
- interface ThemeControllerToggleProps {
87
- defaultTheme?: ThemeName;
88
- lightTheme?: ThemeName;
89
- darkTheme?: ThemeName;
90
- onThemeChange?: (theme: ThemeName) => void;
91
- className?: string;
92
- label?: ReactNode;
93
- size?: "xs" | "sm" | "md" | "lg";
94
- children?: (state: {
95
- isDark: boolean;
96
- checked: boolean;
97
- toggle: (checked: boolean) => void;
98
- }) => ReactNode;
99
- }
100
-
101
- function ThemeControllerToggle({
102
- defaultTheme = "light",
103
- lightTheme = "light",
104
- darkTheme = "dark",
105
- onThemeChange,
106
- className,
107
- label,
108
- size = "md",
109
- children,
110
- }: ThemeControllerToggleProps) {
111
- const [checked, setChecked] = useState(() => {
112
- if (typeof window === "undefined") return false;
113
- const stored = localStorage.getItem("theme") as ThemeName | null;
114
- return (stored || defaultTheme) === darkTheme;
115
- });
116
-
117
- useApplyThemeOnMount(checked ? darkTheme : lightTheme);
118
-
119
- const handleChange = (newChecked: boolean) => {
120
- const newTheme = newChecked ? darkTheme : lightTheme;
121
- setChecked(newChecked);
122
- applyTheme(newTheme);
123
- localStorage.setItem("theme", newTheme);
124
- onThemeChange?.(newTheme);
125
- };
126
-
127
- const isDark = checked;
128
-
129
- if (children) {
130
- return <div className={className}>{children({ isDark, checked, toggle: handleChange })}</div>;
131
- }
132
-
133
- const toggleElement = (
134
- <input
135
- type="checkbox"
136
- className={cn("toggle theme-controller", `toggle-${size}`)}
137
- checked={checked}
138
- value={darkTheme}
139
- onChange={(e) => handleChange(e.target.checked)}
140
- />
141
- );
142
-
143
- if (!label) return <div className={className}>{toggleElement}</div>;
144
-
145
- return (
146
- <label className={cn("flex cursor-pointer items-center gap-3", className)}>
147
- {toggleElement}
148
- <span className="text-base-content">{label}</span>
149
- </label>
150
- );
151
- }
152
-
153
- interface ThemeControllerSelectProps {
154
- themes?: ThemeOption[];
155
- value?: ThemeName;
156
- defaultValue?: ThemeName;
157
- controlled?: boolean;
158
- onThemeChange?: (theme: ThemeName) => void;
159
- className?: string;
160
- size?: "xs" | "sm" | "md" | "lg";
161
- placeholder?: string;
162
- }
163
-
164
- function ThemeControllerSelect({
165
- themes = DEFAULT_THEMES,
166
- value,
167
- defaultValue = "light",
168
- controlled = false,
169
- onThemeChange,
170
- className,
171
- size = "md",
172
- placeholder = "Choose theme",
173
- }: ThemeControllerSelectProps) {
174
- const [internalValue, setInternalValue] = useState<ThemeName>(defaultValue as ThemeName);
175
- const currentValue = controlled ? (value ?? internalValue) : internalValue;
176
- useApplyThemeOnMount(currentValue as ThemeName);
177
-
178
- const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
179
- const newTheme = e.target.value as ThemeName;
180
- if (!controlled) {
181
- setInternalValue(newTheme);
182
- }
183
- applyTheme(newTheme);
184
- localStorage.setItem("theme", newTheme);
185
- onThemeChange?.(newTheme);
186
- };
187
-
188
- return (
189
- <select
190
- className={cn("select theme-controller", `select-${size}`, className)}
191
- value={currentValue}
192
- onChange={handleChange}
193
- data-choose-theme
194
- >
195
- <option value="" disabled>
196
- {placeholder}
197
- </option>
198
- {themes.map((theme) => (
199
- <option key={theme.value} value={theme.value}>
200
- {theme.label}
201
- </option>
202
- ))}
203
- </select>
204
- );
205
- }
206
-
207
- interface ThemeControllerRadioProps {
208
- themes?: ThemeOption[];
209
- value?: ThemeName;
210
- defaultValue?: ThemeName;
211
- controlled?: boolean;
212
- onThemeChange?: (theme: ThemeName) => void;
213
- className?: string;
214
- variant?: "btn" | "tile" | "ghost";
215
- size?: "xs" | "sm" | "md" | "lg";
216
- }
217
-
218
- function ThemeControllerRadio({
219
- themes = DEFAULT_THEMES,
220
- value,
221
- defaultValue = "light",
222
- controlled = false,
223
- onThemeChange,
224
- className,
225
- variant = "btn",
226
- size = "md",
227
- }: ThemeControllerRadioProps) {
228
- const [internalValue, setInternalValue] = useState<ThemeName>(defaultValue as ThemeName);
229
- const currentValue = controlled ? (value ?? internalValue) : internalValue;
230
- useApplyThemeOnMount(currentValue as ThemeName);
231
-
232
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
233
- const newTheme = e.target.value as ThemeName;
234
- if (!controlled) {
235
- setInternalValue(newTheme);
236
- }
237
- applyTheme(newTheme);
238
- localStorage.setItem("theme", newTheme);
239
- onThemeChange?.(newTheme);
240
- };
241
-
242
- const variantClass =
243
- variant === "btn" ? "btn" : variant === "tile" ? "btn btn-tile" : "btn btn-ghost";
244
-
245
- return (
246
- <div className={cn("flex flex-wrap gap-2", className)} role="radiogroup">
247
- {themes.map((theme) => (
248
- <label
249
- key={theme.value}
250
- className={cn(variantClass, `btn-${size}`, currentValue === theme.value && "btn-active")}
251
- >
252
- <input
253
- type="radio"
254
- name="theme"
255
- className="theme-controller hidden"
256
- value={theme.value}
257
- checked={currentValue === theme.value}
258
- onChange={handleChange}
259
- aria-label={theme.label}
260
- />
261
- {theme.label}
262
- </label>
263
- ))}
264
- </div>
265
- );
266
- }
267
-
268
- interface ThemeControllerProps {
269
- mode?: "toggle" | "select" | "radio";
270
- themes?: ThemeOption[];
271
- value?: ThemeName;
272
- defaultValue?: ThemeName;
273
- controlled?: boolean;
274
- onThemeChange?: (theme: ThemeName) => void;
275
- className?: string;
276
- lightTheme?: ThemeName;
277
- darkTheme?: ThemeName;
278
- toggleLabel?: ReactNode;
279
- toggleSize?: "xs" | "sm" | "md" | "lg";
280
- selectSize?: "xs" | "sm" | "md" | "lg";
281
- placeholder?: string;
282
- radioVariant?: "btn" | "tile" | "ghost";
283
- radioSize?: "xs" | "sm" | "md" | "lg";
284
- }
285
-
286
- export function ThemeController({
287
- mode = "toggle",
288
- themes = DEFAULT_THEMES,
289
- value,
290
- defaultValue = "light",
291
- controlled = false,
292
- onThemeChange,
293
- className,
294
- lightTheme = "light",
295
- darkTheme = "dark",
296
- toggleLabel,
297
- toggleSize = "md",
298
- selectSize = "md",
299
- placeholder = "Choose theme",
300
- radioVariant = "btn",
301
- radioSize = "md",
302
- }: ThemeControllerProps) {
303
- const [internalValue, setInternalValue] = useState<ThemeName>(defaultValue as ThemeName);
304
- const currentValue = controlled ? (value ?? internalValue) : internalValue;
305
- useApplyThemeOnMount(currentValue as ThemeName);
306
-
307
- const handleChange = (newTheme: ThemeName) => {
308
- if (!controlled) {
309
- setInternalValue(newTheme);
310
- }
311
- onThemeChange?.(newTheme);
312
- };
313
-
314
- switch (mode) {
315
- case "select":
316
- return (
317
- <ThemeControllerSelect
318
- themes={themes}
319
- value={value}
320
- defaultValue={defaultValue}
321
- controlled={controlled}
322
- onThemeChange={handleChange}
323
- className={className}
324
- size={selectSize}
325
- placeholder={placeholder}
326
- />
327
- );
328
- case "radio":
329
- return (
330
- <ThemeControllerRadio
331
- themes={themes}
332
- value={value}
333
- defaultValue={defaultValue}
334
- controlled={controlled}
335
- onThemeChange={handleChange}
336
- className={className}
337
- variant={radioVariant}
338
- size={radioSize}
339
- />
340
- );
341
- case "toggle":
342
- default:
343
- return (
344
- <ThemeControllerToggle
345
- defaultTheme={defaultValue}
346
- lightTheme={lightTheme}
347
- darkTheme={darkTheme}
348
- onThemeChange={handleChange}
349
- className={className}
350
- label={toggleLabel}
351
- size={toggleSize}
352
- />
353
- );
354
- }
355
- }
356
-
357
- export { ThemeControllerToggle, ThemeControllerSelect, ThemeControllerRadio };
358
- export type { ThemeControllerToggleProps, ThemeControllerSelectProps, ThemeControllerRadioProps };
@@ -1,172 +0,0 @@
1
- "use client";
2
-
3
- import { cn } from "../../node/utils";
4
- import type { InputHTMLAttributes, ReactNode } from "react";
5
- import { forwardRef, useState, useEffect } from "react";
6
-
7
- type ToggleColor =
8
- | "primary"
9
- | "secondary"
10
- | "accent"
11
- | "neutral"
12
- | "success"
13
- | "warning"
14
- | "info"
15
- | "error";
16
- type ToggleSize = "xs" | "sm" | "md" | "lg";
17
-
18
- interface ToggleProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
19
- color?: ToggleColor;
20
- size?: ToggleSize;
21
- label?: ReactNode;
22
- description?: ReactNode;
23
- indeterminate?: boolean;
24
- containerClassName?: string;
25
- labelClassName?: string;
26
- descriptionClassName?: string;
27
- }
28
-
29
- const Toggle = forwardRef<HTMLInputElement, ToggleProps>(function Toggle(
30
- {
31
- color = "primary",
32
- size = "md",
33
- label,
34
- description,
35
- indeterminate = false,
36
- containerClassName,
37
- labelClassName,
38
- descriptionClassName,
39
- className,
40
- disabled,
41
- checked,
42
- defaultChecked,
43
- onChange,
44
- ...props
45
- },
46
- ref
47
- ) {
48
- const colorClass = `toggle-${color}`;
49
- const sizeClass = `toggle-${size}`;
50
-
51
- useEffect(() => {
52
- if (indeterminate && ref) {
53
- const input = (ref as React.RefObject<HTMLInputElement>).current;
54
- if (input) {
55
- input.indeterminate = true;
56
- }
57
- }
58
- }, [ref, indeterminate]);
59
-
60
- const toggleElement = (
61
- <input
62
- ref={ref}
63
- type="checkbox"
64
- className={cn(colorClass, sizeClass, className)}
65
- disabled={disabled}
66
- checked={checked}
67
- defaultChecked={defaultChecked}
68
- onChange={onChange}
69
- data-indeterminate={indeterminate}
70
- {...props}
71
- />
72
- );
73
-
74
- if (!label) {
75
- return toggleElement;
76
- }
77
-
78
- return (
79
- <label
80
- className={cn(
81
- "flex items-start gap-3",
82
- disabled && "cursor-not-allowed opacity-50",
83
- containerClassName
84
- )}
85
- >
86
- <div className="flex items-center pt-0.5">{toggleElement}</div>
87
- {(label || description) && (
88
- <div className="flex flex-col gap-0.5">
89
- {label && (
90
- <span className={cn("text-base-content cursor-pointer", labelClassName)}>{label}</span>
91
- )}
92
- {description && (
93
- <span className={cn("text-base-content/60 text-sm", descriptionClassName)}>
94
- {description}
95
- </span>
96
- )}
97
- </div>
98
- )}
99
- </label>
100
- );
101
- });
102
-
103
- interface ToggleGroupItem {
104
- value: string;
105
- label: ReactNode;
106
- disabled?: boolean;
107
- }
108
-
109
- interface ToggleGroupProps {
110
- items: ToggleGroupItem[];
111
- value?: string[];
112
- defaultValue?: string[];
113
- onChange?: (value: string[]) => void;
114
- color?: ToggleColor;
115
- size?: ToggleSize;
116
- controlled?: boolean;
117
- className?: string;
118
- }
119
-
120
- export function ToggleGroup({
121
- items,
122
- value,
123
- defaultValue = [],
124
- onChange,
125
- color = "primary",
126
- size = "md",
127
- controlled = false,
128
- className,
129
- }: ToggleGroupProps) {
130
- const [internalValue, setInternalValue] = useState<string[]>(defaultValue);
131
- const selectedValues = controlled ? (value ?? internalValue) : internalValue;
132
-
133
- const toggle = (itemValue: string) => {
134
- const next = selectedValues.includes(itemValue)
135
- ? selectedValues.filter((v) => v !== itemValue)
136
- : [...selectedValues, itemValue];
137
-
138
- if (!controlled) {
139
- setInternalValue(next);
140
- }
141
- onChange?.(next);
142
- };
143
-
144
- return (
145
- <div className={cn("flex flex-col gap-3", className)}>
146
- {items.map((item) => {
147
- const isSelected = selectedValues.includes(item.value);
148
- return (
149
- <label
150
- key={item.value}
151
- className={cn(
152
- "flex items-center gap-3",
153
- item.disabled && "cursor-not-allowed opacity-50"
154
- )}
155
- >
156
- <input
157
- type="checkbox"
158
- className={cn(`toggle-${color}`, `toggle-${size}`)}
159
- checked={isSelected}
160
- disabled={item.disabled}
161
- onChange={() => !item.disabled && toggle(item.value)}
162
- />
163
- <span className="text-base-content cursor-pointer">{item.label}</span>
164
- </label>
165
- );
166
- })}
167
- </div>
168
- );
169
- }
170
-
171
- export { Toggle };
172
- export type { ToggleProps, ToggleGroupProps, ToggleGroupItem, ToggleColor, ToggleSize };