@thangnqdev/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -0
- package/dist/Label-DWOlgSNW.d.cts +14 -0
- package/dist/Label-DWOlgSNW.d.ts +14 -0
- package/dist/bento/index.cjs +140 -0
- package/dist/bento/index.cjs.map +1 -0
- package/dist/bento/index.d.cts +4 -0
- package/dist/bento/index.d.ts +4 -0
- package/dist/bento/index.js +4 -0
- package/dist/bento/index.js.map +1 -0
- package/dist/bento/styles.css +46 -0
- package/dist/buttons/index.cjs +365 -0
- package/dist/buttons/index.cjs.map +1 -0
- package/dist/buttons/index.d.cts +4 -0
- package/dist/buttons/index.d.ts +4 -0
- package/dist/buttons/index.js +5 -0
- package/dist/buttons/index.js.map +1 -0
- package/dist/buttons/styles.css +6 -0
- package/dist/chunk-2OXZK6IP.js +514 -0
- package/dist/chunk-2OXZK6IP.js.map +1 -0
- package/dist/chunk-4S6L5V7X.js +15 -0
- package/dist/chunk-4S6L5V7X.js.map +1 -0
- package/dist/chunk-5FZ6ZFII.js +728 -0
- package/dist/chunk-5FZ6ZFII.js.map +1 -0
- package/dist/chunk-66VWUJEI.js +86 -0
- package/dist/chunk-66VWUJEI.js.map +1 -0
- package/dist/chunk-A7P3O5Z5.js +163 -0
- package/dist/chunk-A7P3O5Z5.js.map +1 -0
- package/dist/chunk-ATYGDTMK.js +257 -0
- package/dist/chunk-ATYGDTMK.js.map +1 -0
- package/dist/chunk-C4SUXMI3.js +138 -0
- package/dist/chunk-C4SUXMI3.js.map +1 -0
- package/dist/chunk-EU2LPUYP.js +25 -0
- package/dist/chunk-EU2LPUYP.js.map +1 -0
- package/dist/chunk-F5Q7BTMH.js +1991 -0
- package/dist/chunk-F5Q7BTMH.js.map +1 -0
- package/dist/chunk-GXI2H5XA.js +46 -0
- package/dist/chunk-GXI2H5XA.js.map +1 -0
- package/dist/chunk-H3LCNPMC.js +101 -0
- package/dist/chunk-H3LCNPMC.js.map +1 -0
- package/dist/chunk-IABPUCAQ.js +19 -0
- package/dist/chunk-IABPUCAQ.js.map +1 -0
- package/dist/chunk-KJRBRTQE.js +101 -0
- package/dist/chunk-KJRBRTQE.js.map +1 -0
- package/dist/chunk-LBUBXUYQ.js +40 -0
- package/dist/chunk-LBUBXUYQ.js.map +1 -0
- package/dist/chunk-LWFRUJRU.js +537 -0
- package/dist/chunk-LWFRUJRU.js.map +1 -0
- package/dist/chunk-NFVONJUK.js +882 -0
- package/dist/chunk-NFVONJUK.js.map +1 -0
- package/dist/chunk-PF4ZHGVW.js +284 -0
- package/dist/chunk-PF4ZHGVW.js.map +1 -0
- package/dist/chunk-WAFHC4DQ.js +366 -0
- package/dist/chunk-WAFHC4DQ.js.map +1 -0
- package/dist/chunk-X76KBH4Y.js +71 -0
- package/dist/chunk-X76KBH4Y.js.map +1 -0
- package/dist/core/index.cjs +103 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +3 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/styles.css +128 -0
- package/dist/data-display/index.cjs +1023 -0
- package/dist/data-display/index.cjs.map +1 -0
- package/dist/data-display/index.d.cts +4 -0
- package/dist/data-display/index.d.ts +4 -0
- package/dist/data-display/index.js +6 -0
- package/dist/data-display/index.js.map +1 -0
- package/dist/data-display/styles.css +4 -0
- package/dist/feedback/index.cjs +368 -0
- package/dist/feedback/index.cjs.map +1 -0
- package/dist/feedback/index.d.cts +4 -0
- package/dist/feedback/index.d.ts +4 -0
- package/dist/feedback/index.js +4 -0
- package/dist/feedback/index.js.map +1 -0
- package/dist/feedback/styles.css +4 -0
- package/dist/forms/index.cjs +2287 -0
- package/dist/forms/index.cjs.map +1 -0
- package/dist/forms/index.d.cts +5 -0
- package/dist/forms/index.d.ts +5 -0
- package/dist/forms/index.js +10 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/styles.css +6 -0
- package/dist/glass/index.cjs +165 -0
- package/dist/glass/index.cjs.map +1 -0
- package/dist/glass/index.d.cts +4 -0
- package/dist/glass/index.d.ts +4 -0
- package/dist/glass/index.js +4 -0
- package/dist/glass/index.js.map +1 -0
- package/dist/glass/styles.css +81 -0
- package/dist/index-BTvkbCIj.d.cts +146 -0
- package/dist/index-BTvkbCIj.d.ts +146 -0
- package/dist/index-BkRqGNG1.d.cts +201 -0
- package/dist/index-BkRqGNG1.d.ts +201 -0
- package/dist/index-BlPcGeW-.d.cts +45 -0
- package/dist/index-BlPcGeW-.d.ts +45 -0
- package/dist/index-BzOCqvC6.d.cts +32 -0
- package/dist/index-BzOCqvC6.d.ts +32 -0
- package/dist/index-CCeWYAvn.d.cts +83 -0
- package/dist/index-COQ7uwt9.d.cts +44 -0
- package/dist/index-COQ7uwt9.d.ts +44 -0
- package/dist/index-CenuF_P2.d.cts +221 -0
- package/dist/index-CenuF_P2.d.ts +221 -0
- package/dist/index-Cle6QzQl.d.ts +371 -0
- package/dist/index-D6fE7w-b.d.cts +90 -0
- package/dist/index-D6fE7w-b.d.ts +90 -0
- package/dist/index-DR8cfJUG.d.ts +83 -0
- package/dist/index-DzcWh1Hs.d.cts +131 -0
- package/dist/index-DzcWh1Hs.d.ts +131 -0
- package/dist/index-TyK-Ldpy.d.cts +371 -0
- package/dist/index-ugj5s6a3.d.cts +95 -0
- package/dist/index-ugj5s6a3.d.ts +95 -0
- package/dist/index.cjs +6400 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +16 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.cjs +516 -0
- package/dist/layout/index.cjs.map +1 -0
- package/dist/layout/index.d.cts +4 -0
- package/dist/layout/index.d.ts +4 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/styles.css +8 -0
- package/dist/navigation/index.cjs +673 -0
- package/dist/navigation/index.cjs.map +1 -0
- package/dist/navigation/index.d.cts +2 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +8 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/styles.css +4 -0
- package/dist/overlays/index.cjs +954 -0
- package/dist/overlays/index.cjs.map +1 -0
- package/dist/overlays/index.d.cts +4 -0
- package/dist/overlays/index.d.ts +4 -0
- package/dist/overlays/index.js +9 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/overlays/styles.css +8 -0
- package/dist/styles.css +333 -0
- package/dist/typography/index.cjs +311 -0
- package/dist/typography/index.cjs.map +1 -0
- package/dist/typography/index.d.cts +5 -0
- package/dist/typography/index.d.ts +5 -0
- package/dist/typography/index.js +6 -0
- package/dist/typography/index.js.map +1 -0
- package/dist/typography/styles.css +5 -0
- package/llms.txt +113 -0
- package/package.json +116 -0
- package/src/bento/BentoCard.tsx +140 -0
- package/src/bento/BentoGrid.tsx +50 -0
- package/src/bento/index.ts +2 -0
- package/src/bento/tokens.css +46 -0
- package/src/buttons/Button.tsx +104 -0
- package/src/buttons/ButtonGroup.tsx +43 -0
- package/src/buttons/IconButton.tsx +79 -0
- package/src/buttons/SplitButton.tsx +86 -0
- package/src/buttons/ToggleButton.tsx +157 -0
- package/src/buttons/index.ts +5 -0
- package/src/buttons/tokens.css +6 -0
- package/src/core/ThemeProvider.tsx +123 -0
- package/src/core/index.ts +2 -0
- package/src/core/tokens.css +128 -0
- package/src/data-display/Accordion.tsx +147 -0
- package/src/data-display/Avatar.tsx +134 -0
- package/src/data-display/Card.tsx +112 -0
- package/src/data-display/Carousel.tsx +130 -0
- package/src/data-display/Chip.tsx +74 -0
- package/src/data-display/DataTablePro.tsx +356 -0
- package/src/data-display/StatCard.tsx +60 -0
- package/src/data-display/Table.tsx +74 -0
- package/src/data-display/Timeline.tsx +62 -0
- package/src/data-display/TreeView.tsx +140 -0
- package/src/data-display/index.ts +10 -0
- package/src/data-display/tokens.css +4 -0
- package/src/feedback/Alert.tsx +80 -0
- package/src/feedback/EmptyState.tsx +45 -0
- package/src/feedback/ProgressBar.tsx +174 -0
- package/src/feedback/Skeleton.tsx +66 -0
- package/src/feedback/Spinner.tsx +65 -0
- package/src/feedback/index.ts +5 -0
- package/src/feedback/tokens.css +4 -0
- package/src/forms/Checkbox.tsx +69 -0
- package/src/forms/CheckboxGroup.tsx +29 -0
- package/src/forms/Combobox.tsx +368 -0
- package/src/forms/DatePicker.tsx +480 -0
- package/src/forms/FileUpload.tsx +222 -0
- package/src/forms/FormField.tsx +115 -0
- package/src/forms/Input.tsx +151 -0
- package/src/forms/MaskedInput.tsx +97 -0
- package/src/forms/NumberInput.tsx +248 -0
- package/src/forms/PasswordInput.tsx +35 -0
- package/src/forms/PinInput.tsx +125 -0
- package/src/forms/Radio.tsx +142 -0
- package/src/forms/RangeSlider.tsx +111 -0
- package/src/forms/SearchInput.tsx +34 -0
- package/src/forms/Select.tsx +103 -0
- package/src/forms/Slider.tsx +85 -0
- package/src/forms/Switch.tsx +95 -0
- package/src/forms/Textarea.tsx +114 -0
- package/src/forms/index.ts +18 -0
- package/src/forms/tokens.css +6 -0
- package/src/glass/GlassBadge.tsx +61 -0
- package/src/glass/GlassButton.tsx +59 -0
- package/src/glass/GlassCard.tsx +71 -0
- package/src/glass/index.ts +3 -0
- package/src/glass/tokens.css +81 -0
- package/src/index.ts +26 -0
- package/src/layout/AspectRatio.tsx +39 -0
- package/src/layout/Container.tsx +53 -0
- package/src/layout/Divider.tsx +61 -0
- package/src/layout/Flex.tsx +79 -0
- package/src/layout/Grid.tsx +124 -0
- package/src/layout/PageLayout.tsx +59 -0
- package/src/layout/ScrollArea.tsx +43 -0
- package/src/layout/Section.tsx +47 -0
- package/src/layout/Spacer.tsx +30 -0
- package/src/layout/Stack.tsx +88 -0
- package/src/layout/index.ts +10 -0
- package/src/layout/tokens.css +8 -0
- package/src/navigation/Breadcrumb.tsx +73 -0
- package/src/navigation/CommandPalette.tsx +142 -0
- package/src/navigation/DropdownMenu.tsx +160 -0
- package/src/navigation/Pagination.tsx +114 -0
- package/src/navigation/Stepper.tsx +93 -0
- package/src/navigation/Tabs.tsx +133 -0
- package/src/navigation/index.ts +6 -0
- package/src/navigation/tokens.css +4 -0
- package/src/overlays/AlertDialog.tsx +54 -0
- package/src/overlays/BottomSheet.tsx +123 -0
- package/src/overlays/ContextMenu.tsx +119 -0
- package/src/overlays/Drawer.tsx +145 -0
- package/src/overlays/FocusTrap.tsx +49 -0
- package/src/overlays/HoverCard.tsx +72 -0
- package/src/overlays/Modal.tsx +219 -0
- package/src/overlays/Popover.tsx +126 -0
- package/src/overlays/Portal.tsx +23 -0
- package/src/overlays/Toast.tsx +137 -0
- package/src/overlays/Tooltip.tsx +100 -0
- package/src/overlays/index.ts +11 -0
- package/src/overlays/tokens.css +8 -0
- package/src/styles/tokens.css +2 -0
- package/src/typography/Blockquote.tsx +32 -0
- package/src/typography/Code.tsx +89 -0
- package/src/typography/Heading.tsx +56 -0
- package/src/typography/Kbd.tsx +25 -0
- package/src/typography/Label.tsx +47 -0
- package/src/typography/Link.tsx +56 -0
- package/src/typography/Text.tsx +62 -0
- package/src/typography/index.ts +7 -0
- package/src/typography/tokens.css +5 -0
- package/src/utils/cn.ts +6 -0
- package/ui-contract.json +62 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { ChevronDown } from 'lucide-react';
|
|
4
|
+
import { useFormField } from './FormField';
|
|
5
|
+
import { cn } from '../utils/cn';
|
|
6
|
+
|
|
7
|
+
export const selectVariants = cva(
|
|
8
|
+
'w-full rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] text-[var(--ui-text-primary)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 appearance-none pr-10',
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
sm: 'h-8 px-2.5 text-xs',
|
|
13
|
+
md: 'h-10 px-3.5 text-sm',
|
|
14
|
+
lg: 'h-12 px-4 text-base',
|
|
15
|
+
},
|
|
16
|
+
variant: {
|
|
17
|
+
default: 'border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]',
|
|
18
|
+
filled: 'bg-[var(--ui-bg-secondary)] border-transparent hover:bg-[var(--ui-bg-tertiary)]',
|
|
19
|
+
},
|
|
20
|
+
isInvalid: {
|
|
21
|
+
true: 'border-[var(--ui-error)] focus-visible:ring-[var(--ui-error)]',
|
|
22
|
+
false: '',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
size: 'md',
|
|
27
|
+
variant: 'default',
|
|
28
|
+
isInvalid: false,
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface SelectOption {
|
|
34
|
+
value: string | number;
|
|
35
|
+
label: string;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SelectProps
|
|
40
|
+
extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'size'>,
|
|
41
|
+
VariantProps<typeof selectVariants> {
|
|
42
|
+
options?: SelectOption[];
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
|
|
47
|
+
(
|
|
48
|
+
{
|
|
49
|
+
className,
|
|
50
|
+
size,
|
|
51
|
+
variant,
|
|
52
|
+
isInvalid: customInvalid,
|
|
53
|
+
options,
|
|
54
|
+
placeholder,
|
|
55
|
+
disabled,
|
|
56
|
+
children,
|
|
57
|
+
id,
|
|
58
|
+
...props
|
|
59
|
+
},
|
|
60
|
+
ref
|
|
61
|
+
) => {
|
|
62
|
+
const formField = useFormField();
|
|
63
|
+
const isInvalid = customInvalid ?? formField?.isInvalid ?? false;
|
|
64
|
+
const isDisabled = disabled ?? formField?.isDisabled ?? false;
|
|
65
|
+
const inputId = id || formField?.id;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="relative w-full flex items-center">
|
|
69
|
+
<select
|
|
70
|
+
ref={ref}
|
|
71
|
+
id={inputId}
|
|
72
|
+
disabled={isDisabled}
|
|
73
|
+
aria-invalid={isInvalid}
|
|
74
|
+
aria-describedby={
|
|
75
|
+
isInvalid
|
|
76
|
+
? formField?.errorId
|
|
77
|
+
: formField?.descriptionId || undefined
|
|
78
|
+
}
|
|
79
|
+
className={cn(selectVariants({ size, variant, isInvalid }), className)}
|
|
80
|
+
{...props}
|
|
81
|
+
>
|
|
82
|
+
{placeholder && (
|
|
83
|
+
<option value="" disabled hidden>
|
|
84
|
+
{placeholder}
|
|
85
|
+
</option>
|
|
86
|
+
)}
|
|
87
|
+
{options
|
|
88
|
+
? options.map((opt) => (
|
|
89
|
+
<option key={opt.value} value={opt.value} disabled={opt.disabled}>
|
|
90
|
+
{opt.label}
|
|
91
|
+
</option>
|
|
92
|
+
))
|
|
93
|
+
: children}
|
|
94
|
+
</select>
|
|
95
|
+
<div className="absolute right-3 flex items-center pointer-events-none text-[var(--ui-text-muted)]">
|
|
96
|
+
<ChevronDown className="w-4 h-4" />
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
Select.displayName = 'Select';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { cn } from '../utils/cn';
|
|
3
|
+
|
|
4
|
+
export interface SliderProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue'> {
|
|
5
|
+
value?: number;
|
|
6
|
+
defaultValue?: number;
|
|
7
|
+
min?: number;
|
|
8
|
+
max?: number;
|
|
9
|
+
step?: number;
|
|
10
|
+
showValue?: boolean;
|
|
11
|
+
formatValue?: (val: number) => string;
|
|
12
|
+
onChange?: (val: number) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Slider = forwardRef<HTMLInputElement, SliderProps>(
|
|
16
|
+
(
|
|
17
|
+
{
|
|
18
|
+
className,
|
|
19
|
+
value: controlledValue,
|
|
20
|
+
defaultValue = 0,
|
|
21
|
+
min = 0,
|
|
22
|
+
max = 100,
|
|
23
|
+
step = 1,
|
|
24
|
+
showValue = false,
|
|
25
|
+
formatValue = (v) => `${v}`,
|
|
26
|
+
onChange,
|
|
27
|
+
disabled,
|
|
28
|
+
...props
|
|
29
|
+
},
|
|
30
|
+
ref
|
|
31
|
+
) => {
|
|
32
|
+
const [val, setVal] = React.useState<number>(defaultValue);
|
|
33
|
+
const currentValue = controlledValue !== undefined ? controlledValue : val;
|
|
34
|
+
|
|
35
|
+
const percentage = Math.min(100, Math.max(0, ((currentValue - min) / (max - min)) * 100));
|
|
36
|
+
|
|
37
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
38
|
+
const num = Number(e.target.value);
|
|
39
|
+
setVal(num);
|
|
40
|
+
onChange?.(num);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className={cn('w-full flex items-center gap-4', className)}>
|
|
45
|
+
<div className="relative w-full flex items-center h-6 select-none touch-none">
|
|
46
|
+
{/* Rail */}
|
|
47
|
+
<div className="w-full h-2 rounded-full bg-[var(--ui-border-default)] overflow-hidden relative">
|
|
48
|
+
{/* Fill Track */}
|
|
49
|
+
<div
|
|
50
|
+
className="h-full bg-[var(--ui-primary)] transition-all duration-75"
|
|
51
|
+
style={{ width: `${percentage}%` }}
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<input
|
|
56
|
+
ref={ref}
|
|
57
|
+
type="range"
|
|
58
|
+
min={min}
|
|
59
|
+
max={max}
|
|
60
|
+
step={step}
|
|
61
|
+
value={currentValue}
|
|
62
|
+
disabled={disabled}
|
|
63
|
+
onChange={handleChange}
|
|
64
|
+
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer disabled:cursor-not-allowed"
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
|
|
68
|
+
{/* Thumb visual */}
|
|
69
|
+
<div
|
|
70
|
+
className="absolute w-5 h-5 rounded-full bg-white border-2 border-[var(--ui-primary)] shadow-md pointer-events-none transition-transform duration-75 -translate-x-1/2"
|
|
71
|
+
style={{ left: `${percentage}%` }}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
{showValue && (
|
|
76
|
+
<span className="text-xs font-semibold font-mono text-[var(--ui-text-secondary)] min-w-[2.5rem] text-right">
|
|
77
|
+
{formatValue(currentValue)}
|
|
78
|
+
</span>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
Slider.displayName = 'Slider';
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { forwardRef, useState } from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { useFormField } from './FormField';
|
|
4
|
+
import { cn } from '../utils/cn';
|
|
5
|
+
|
|
6
|
+
export const switchVariants = cva(
|
|
7
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-all duration-200 focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[#34c759]/30 disabled:cursor-not-allowed disabled:opacity-50 bg-[var(--ui-border-strong)] data-[state=checked]:bg-[#34c759] dark:data-[state=checked]:bg-[#30d158] shadow-inner',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: 'h-5 w-9 p-0.5',
|
|
12
|
+
md: 'h-6.5 w-12 p-0.5',
|
|
13
|
+
lg: 'h-8 w-15 p-1',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: {
|
|
17
|
+
size: 'md',
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
export const switchThumbVariants = cva(
|
|
23
|
+
'pointer-events-none block rounded-full bg-white shadow-[0_2px_4px_rgba(0,0,0,0.25)] ring-0 transition-transform duration-200 ease-out data-[state=unchecked]:translate-x-0',
|
|
24
|
+
{
|
|
25
|
+
variants: {
|
|
26
|
+
size: {
|
|
27
|
+
sm: 'h-4 w-4 data-[state=checked]:translate-x-4',
|
|
28
|
+
md: 'h-5.5 w-5.5 data-[state=checked]:translate-x-5.5',
|
|
29
|
+
lg: 'h-6 w-6 data-[state=checked]:translate-x-7',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: {
|
|
33
|
+
size: 'md',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export interface SwitchProps
|
|
39
|
+
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'>,
|
|
40
|
+
VariantProps<typeof switchVariants> {
|
|
41
|
+
label?: React.ReactNode;
|
|
42
|
+
checked?: boolean;
|
|
43
|
+
defaultChecked?: boolean;
|
|
44
|
+
onCheckedChange?: (checked: boolean) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
48
|
+
({ className, size, label, checked, defaultChecked, onCheckedChange, disabled, id, onChange, ...props }, ref) => {
|
|
49
|
+
const isControlled = checked !== undefined;
|
|
50
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked ?? false);
|
|
51
|
+
const isChecked = isControlled ? checked : internalChecked;
|
|
52
|
+
|
|
53
|
+
const formField = useFormField();
|
|
54
|
+
const isDisabled = disabled ?? formField?.isDisabled ?? false;
|
|
55
|
+
const switchId = id || formField?.id;
|
|
56
|
+
|
|
57
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
58
|
+
if (!isControlled) {
|
|
59
|
+
setInternalChecked(e.target.checked);
|
|
60
|
+
}
|
|
61
|
+
onChange?.(e);
|
|
62
|
+
onCheckedChange?.(e.target.checked);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<label className={cn('inline-flex items-center gap-3 select-none cursor-pointer', isDisabled && 'cursor-not-allowed opacity-50')}>
|
|
67
|
+
<div className="relative flex items-center">
|
|
68
|
+
<input
|
|
69
|
+
ref={ref}
|
|
70
|
+
type="checkbox"
|
|
71
|
+
role="switch"
|
|
72
|
+
id={switchId}
|
|
73
|
+
disabled={isDisabled}
|
|
74
|
+
checked={isChecked}
|
|
75
|
+
onChange={handleChange}
|
|
76
|
+
className="sr-only peer"
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
<div
|
|
80
|
+
data-state={isChecked ? 'checked' : 'unchecked'}
|
|
81
|
+
className={cn(switchVariants({ size }), className)}
|
|
82
|
+
>
|
|
83
|
+
<span
|
|
84
|
+
data-state={isChecked ? 'checked' : 'unchecked'}
|
|
85
|
+
className={cn(switchThumbVariants({ size }))}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
{label && <span className="text-sm font-medium text-[var(--ui-text-primary)]">{label}</span>}
|
|
90
|
+
</label>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
Switch.displayName = 'Switch';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React, { forwardRef, useEffect, useRef } from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { useFormField } from './FormField';
|
|
4
|
+
import { cn } from '../utils/cn';
|
|
5
|
+
|
|
6
|
+
export const textareaVariants = cva(
|
|
7
|
+
'w-full rounded-[var(--ui-radius-md)] border bg-[var(--ui-bg-surface)] p-3 text-sm text-[var(--ui-text-primary)] placeholder:text-[var(--ui-text-muted)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary)] disabled:cursor-not-allowed disabled:opacity-50 min-h-[80px]',
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: 'border-[var(--ui-border-default)] hover:border-[var(--ui-border-strong)]',
|
|
12
|
+
filled: 'bg-[var(--ui-bg-secondary)] border-transparent hover:bg-[var(--ui-bg-tertiary)]',
|
|
13
|
+
},
|
|
14
|
+
isInvalid: {
|
|
15
|
+
true: 'border-[var(--ui-error)] focus-visible:ring-[var(--ui-error)]',
|
|
16
|
+
false: '',
|
|
17
|
+
},
|
|
18
|
+
resize: {
|
|
19
|
+
none: 'resize-none',
|
|
20
|
+
vertical: 'resize-y',
|
|
21
|
+
horizontal: 'resize-x',
|
|
22
|
+
both: 'resize',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
variant: 'default',
|
|
27
|
+
isInvalid: false,
|
|
28
|
+
resize: 'vertical',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface TextareaProps
|
|
34
|
+
extends React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
35
|
+
VariantProps<typeof textareaVariants> {
|
|
36
|
+
autoResize?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
40
|
+
(
|
|
41
|
+
{
|
|
42
|
+
className,
|
|
43
|
+
variant,
|
|
44
|
+
isInvalid: customInvalid,
|
|
45
|
+
resize,
|
|
46
|
+
autoResize = false,
|
|
47
|
+
disabled,
|
|
48
|
+
value,
|
|
49
|
+
onChange,
|
|
50
|
+
id,
|
|
51
|
+
...props
|
|
52
|
+
},
|
|
53
|
+
ref
|
|
54
|
+
) => {
|
|
55
|
+
const formField = useFormField();
|
|
56
|
+
const isInvalid = customInvalid ?? formField?.isInvalid ?? false;
|
|
57
|
+
const isDisabled = disabled ?? formField?.isDisabled ?? false;
|
|
58
|
+
const inputId = id || formField?.id;
|
|
59
|
+
|
|
60
|
+
const innerRef = useRef<HTMLTextAreaElement | null>(null);
|
|
61
|
+
|
|
62
|
+
const setMergedRef = (element: HTMLTextAreaElement | null) => {
|
|
63
|
+
innerRef.current = element;
|
|
64
|
+
if (typeof ref === 'function') {
|
|
65
|
+
ref(element);
|
|
66
|
+
} else if (ref) {
|
|
67
|
+
(ref as React.MutableRefObject<HTMLTextAreaElement | null>).current = element;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const handleInput = () => {
|
|
72
|
+
if (autoResize && innerRef.current) {
|
|
73
|
+
innerRef.current.style.height = 'auto';
|
|
74
|
+
innerRef.current.style.height = `${innerRef.current.scrollHeight}px`;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (autoResize && innerRef.current) {
|
|
80
|
+
handleInput();
|
|
81
|
+
}
|
|
82
|
+
}, [value, autoResize]);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<textarea
|
|
86
|
+
ref={setMergedRef}
|
|
87
|
+
id={inputId}
|
|
88
|
+
disabled={isDisabled}
|
|
89
|
+
value={value}
|
|
90
|
+
onChange={(e) => {
|
|
91
|
+
handleInput();
|
|
92
|
+
onChange?.(e);
|
|
93
|
+
}}
|
|
94
|
+
aria-invalid={isInvalid}
|
|
95
|
+
aria-describedby={
|
|
96
|
+
isInvalid
|
|
97
|
+
? formField?.errorId
|
|
98
|
+
: formField?.descriptionId || undefined
|
|
99
|
+
}
|
|
100
|
+
className={cn(
|
|
101
|
+
textareaVariants({
|
|
102
|
+
variant,
|
|
103
|
+
isInvalid,
|
|
104
|
+
resize: autoResize ? 'none' : resize,
|
|
105
|
+
}),
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
Textarea.displayName = 'Textarea';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './FormField';
|
|
2
|
+
export * from './Input';
|
|
3
|
+
export * from './SearchInput';
|
|
4
|
+
export * from './PasswordInput';
|
|
5
|
+
export * from './Textarea';
|
|
6
|
+
export * from './Select';
|
|
7
|
+
export * from './Checkbox';
|
|
8
|
+
export * from './CheckboxGroup';
|
|
9
|
+
export * from './Radio';
|
|
10
|
+
export * from './Switch';
|
|
11
|
+
export * from './Slider';
|
|
12
|
+
export * from './RangeSlider';
|
|
13
|
+
export * from './PinInput';
|
|
14
|
+
export * from './DatePicker';
|
|
15
|
+
export * from './Combobox';
|
|
16
|
+
export * from './FileUpload';
|
|
17
|
+
export * from './NumberInput';
|
|
18
|
+
export * from './MaskedInput';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const glassBadgeVariants = cva(
|
|
6
|
+
'inline-flex items-center font-sans font-semibold tracking-tight rounded-full backdrop-blur-xl border transition-all select-none',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
'bg-[var(--ui-bg-secondary)]/80 text-[var(--ui-text-primary)] border-[var(--ui-border-default)] shadow-xs',
|
|
12
|
+
emerald:
|
|
13
|
+
'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400 border-emerald-500/25 shadow-xs shadow-emerald-500/10',
|
|
14
|
+
cyan:
|
|
15
|
+
'bg-blue-500/15 text-blue-600 dark:text-blue-400 border-blue-500/25 shadow-xs shadow-blue-500/10',
|
|
16
|
+
purple:
|
|
17
|
+
'bg-purple-500/15 text-purple-600 dark:text-purple-400 border-purple-500/25 shadow-xs shadow-purple-500/10',
|
|
18
|
+
amber:
|
|
19
|
+
'bg-amber-500/15 text-amber-600 dark:text-amber-400 border-amber-500/25 shadow-xs shadow-amber-500/10',
|
|
20
|
+
rose:
|
|
21
|
+
'bg-rose-500/15 text-rose-600 dark:text-rose-400 border-rose-500/25 shadow-xs shadow-rose-500/10',
|
|
22
|
+
},
|
|
23
|
+
size: {
|
|
24
|
+
sm: 'text-[10px] px-2 py-0.5 gap-1',
|
|
25
|
+
md: 'text-[11px] px-2.5 py-0.5 gap-1.5',
|
|
26
|
+
lg: 'text-xs px-3 py-1 gap-1.5',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: 'default',
|
|
31
|
+
size: 'md',
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface GlassBadgeProps
|
|
37
|
+
extends React.HTMLAttributes<HTMLSpanElement>,
|
|
38
|
+
VariantProps<typeof glassBadgeVariants> {
|
|
39
|
+
dot?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const GlassBadge: React.FC<GlassBadgeProps> = ({
|
|
43
|
+
className,
|
|
44
|
+
variant,
|
|
45
|
+
size,
|
|
46
|
+
dot = false,
|
|
47
|
+
children,
|
|
48
|
+
...props
|
|
49
|
+
}) => {
|
|
50
|
+
return (
|
|
51
|
+
<span className={cn(glassBadgeVariants({ variant, size, className }))} {...props}>
|
|
52
|
+
{dot && (
|
|
53
|
+
<span
|
|
54
|
+
className="h-1.5 w-1.5 rounded-full bg-current motion-safe:animate-pulse shrink-0"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
/>
|
|
57
|
+
)}
|
|
58
|
+
<span>{children}</span>
|
|
59
|
+
</span>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const glassButtonVariants = cva(
|
|
6
|
+
'group relative inline-flex items-center justify-center font-medium tracking-tight transition-all duration-300 active:scale-[0.97] disabled:pointer-events-none disabled:opacity-40 overflow-hidden cursor-pointer select-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900 dark:focus-visible:ring-white/90 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--ui-focus-ring-offset)]',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default:
|
|
11
|
+
'bg-[var(--ui-glass-btn-default-bg)] hover:bg-[var(--ui-glass-btn-default-hover)] backdrop-blur-md border border-[var(--ui-glass-btn-default-border)] text-[var(--ui-glass-btn-default-text)] shadow-sm dark:shadow-[0_4px_24px_rgba(0,0,0,0.35)] hover:border-black/20 dark:hover:border-white/35',
|
|
12
|
+
primary:
|
|
13
|
+
'bg-slate-900 text-white dark:bg-white dark:text-slate-950 font-semibold shadow-md hover:bg-slate-800 dark:hover:bg-slate-100 border border-slate-700 dark:border-white shadow-[0_4px_20px_rgba(0,0,0,0.2)] dark:shadow-[0_0_24px_rgba(255,255,255,0.25)]',
|
|
14
|
+
luminescent:
|
|
15
|
+
'bg-gradient-to-r from-slate-800/80 to-slate-900/80 text-white dark:from-white/15 dark:to-white/5 dark:text-white backdrop-blur-xl border border-white/20 dark:border-white/30 shadow-[0_0_20px_-4px_rgba(255,255,255,0.15)] hover:border-white/50 hover:shadow-[0_0_30px_-4px_rgba(255,255,255,0.3)]',
|
|
16
|
+
ghost:
|
|
17
|
+
'bg-transparent hover:bg-black/5 dark:hover:bg-white/10 backdrop-blur-sm border border-transparent hover:border-black/10 dark:hover:border-white/15 text-[var(--ui-glass-text-secondary)] hover:text-[var(--ui-glass-text-primary)]',
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
sm: 'h-8 px-3.5 text-xs rounded-xl gap-1.5',
|
|
21
|
+
md: 'h-10 px-5 text-sm rounded-2xl gap-2',
|
|
22
|
+
lg: 'h-12 px-7 text-base rounded-2xl gap-2.5',
|
|
23
|
+
icon: 'h-10 w-10 rounded-2xl',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
variant: 'default',
|
|
28
|
+
size: 'md',
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface GlassButtonProps
|
|
34
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
+
VariantProps<typeof glassButtonVariants> {
|
|
36
|
+
shimmer?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const GlassButton = forwardRef<HTMLButtonElement, GlassButtonProps>(
|
|
40
|
+
({ className, variant, size, shimmer = true, children, ...props }, ref) => {
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
ref={ref}
|
|
44
|
+
className={cn(glassButtonVariants({ variant, size, className }))}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
{shimmer && (
|
|
48
|
+
<span
|
|
49
|
+
className="pointer-events-none absolute -inset-full top-0 block -rotate-45 bg-gradient-to-r from-transparent via-white/25 dark:via-white/15 to-transparent transition-all duration-700 motion-safe:group-hover:translate-x-full"
|
|
50
|
+
aria-hidden="true"
|
|
51
|
+
/>
|
|
52
|
+
)}
|
|
53
|
+
<span className="relative z-10 inline-flex items-center gap-2">{children}</span>
|
|
54
|
+
</button>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
GlassButton.displayName = 'GlassButton';
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils/cn';
|
|
4
|
+
|
|
5
|
+
const glassCardVariants = cva(
|
|
6
|
+
'relative overflow-hidden transition-all duration-300 text-[var(--ui-glass-text-primary)]',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
intensity: {
|
|
10
|
+
subtle:
|
|
11
|
+
'bg-[var(--ui-glass-subtle-bg)] backdrop-blur-[var(--ui-glass-subtle-blur)] border border-[var(--ui-glass-subtle-border)]',
|
|
12
|
+
standard:
|
|
13
|
+
'bg-[var(--ui-glass-standard-bg)] backdrop-blur-[var(--ui-glass-standard-blur)] border border-[var(--ui-glass-standard-border)] shadow-[var(--ui-glass-standard-shadow)]',
|
|
14
|
+
frosted:
|
|
15
|
+
'bg-[var(--ui-glass-frosted-bg)] backdrop-blur-[var(--ui-glass-frosted-blur)] border border-[var(--ui-glass-frosted-border)] shadow-[var(--ui-glass-frosted-shadow)]',
|
|
16
|
+
acrylic:
|
|
17
|
+
'bg-[image:var(--ui-glass-acrylic-bg)] backdrop-blur-[var(--ui-glass-acrylic-blur)] border border-[var(--ui-glass-acrylic-border)] shadow-[var(--ui-glass-acrylic-shadow)]',
|
|
18
|
+
},
|
|
19
|
+
glow: {
|
|
20
|
+
none: '',
|
|
21
|
+
cyan: 'hover:shadow-[0_0_40px_-8px_rgba(56,189,248,0.3)] hover:border-sky-400/40',
|
|
22
|
+
purple: 'hover:shadow-[0_0_40px_-8px_rgba(192,132,252,0.3)] hover:border-purple-400/40',
|
|
23
|
+
white: 'hover:shadow-[0_0_35px_-8px_rgba(255,255,255,0.25)] hover:border-white/30',
|
|
24
|
+
},
|
|
25
|
+
radius: {
|
|
26
|
+
md: 'rounded-xl',
|
|
27
|
+
lg: 'rounded-2xl',
|
|
28
|
+
xl: 'rounded-3xl',
|
|
29
|
+
},
|
|
30
|
+
padding: {
|
|
31
|
+
sm: 'p-4',
|
|
32
|
+
md: 'p-6',
|
|
33
|
+
lg: 'p-8',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: {
|
|
37
|
+
intensity: 'standard',
|
|
38
|
+
glow: 'none',
|
|
39
|
+
radius: 'lg',
|
|
40
|
+
padding: 'md',
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export interface GlassCardProps
|
|
46
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
47
|
+
VariantProps<typeof glassCardVariants> {
|
|
48
|
+
specularLight?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const GlassCard = forwardRef<HTMLDivElement, GlassCardProps>(
|
|
52
|
+
({ className, intensity, glow, radius, padding, specularLight = true, children, ...props }, ref) => {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
ref={ref}
|
|
56
|
+
className={cn(glassCardVariants({ intensity, glow, radius, padding, className }))}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{specularLight && (
|
|
60
|
+
<div
|
|
61
|
+
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-[image:var(--ui-glass-specular-light)] opacity-80"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
GlassCard.displayName = 'GlassCard';
|