@docubook/flame 1.0.0-beta.80 → 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.
- package/.docu/components/Anchor.tsx +1 -1
- package/.docu/components/Breadcrumb.tsx +6 -1
- package/.docu/components/Context.tsx +2 -1
- package/.docu/components/Navbar.tsx +2 -2
- package/.docu/components/Pagination.tsx +15 -2
- package/.docu/components/Search.tsx +4 -10
- package/.docu/components/Sidebar.tsx +15 -4
- package/.docu/components/Theme.tsx +1 -1
- package/.docu/components/registry.ts +21 -87
- package/.docu/node/build.ts +6 -2
- package/.docu/node/client.ts +1 -8
- package/.docu/node/html.ts +12 -0
- package/.docu/node/parse-tocs.ts +10 -0
- package/.docu/node/search-indexer.ts +16 -2
- package/.docu/node/server.ts +1 -13
- package/.docu/node/utils.ts +6 -4
- package/.docu/pages/docs/[[...slug]].tsx +6 -1
- package/.docu/styles/globals.css +8 -0
- package/README.md +2 -2
- package/package.json +9 -5
- package/template/docs/index.mdx +123 -17
- package/.docu/components/base/breadcrumbs.tsx +0 -64
- package/.docu/components/base/collapse.tsx +0 -186
- package/.docu/components/base/drawer.tsx +0 -164
- package/.docu/components/base/dropdown.tsx +0 -94
- package/.docu/components/base/input.tsx +0 -53
- package/.docu/components/base/kbd.tsx +0 -51
- package/.docu/components/base/modal.tsx +0 -56
- package/.docu/components/base/navbar.tsx +0 -132
- package/.docu/components/base/pagination/index.ts +0 -21
- package/.docu/components/base/pagination/pagination-docs.tsx +0 -39
- package/.docu/components/base/pagination/pagination-numbers.tsx +0 -379
- package/.docu/components/base/pagination/types.ts +0 -129
- package/.docu/components/base/theme-controller.tsx +0 -358
- package/.docu/components/base/toggle.tsx +0 -172
|
@@ -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 };
|