@optilogic/core 1.0.0-beta.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 +107 -0
- package/dist/index.cjs +6003 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2310 -0
- package/dist/index.d.ts +2310 -0
- package/dist/index.js +5828 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +96 -0
- package/dist/tailwind-preset.cjs +106 -0
- package/dist/tailwind-preset.cjs.map +1 -0
- package/dist/tailwind-preset.d.cts +23 -0
- package/dist/tailwind-preset.d.ts +23 -0
- package/dist/tailwind-preset.js +101 -0
- package/dist/tailwind-preset.js.map +1 -0
- package/package.json +154 -0
- package/src/components/accordion.tsx +187 -0
- package/src/components/alert-dialog.tsx +143 -0
- package/src/components/autocomplete.tsx +271 -0
- package/src/components/badge.tsx +62 -0
- package/src/components/button.tsx +85 -0
- package/src/components/calendar.tsx +235 -0
- package/src/components/card.tsx +94 -0
- package/src/components/checkbox.tsx +77 -0
- package/src/components/chip.tsx +77 -0
- package/src/components/confirmation-modal.tsx +195 -0
- package/src/components/context-menu.tsx +406 -0
- package/src/components/copy-button.tsx +84 -0
- package/src/components/data-grid/DataGrid.tsx +1027 -0
- package/src/components/data-grid/components/CellEditor.tsx +346 -0
- package/src/components/data-grid/components/FilterPopover.tsx +459 -0
- package/src/components/data-grid/components/HeaderCell.tsx +207 -0
- package/src/components/data-grid/components/index.ts +14 -0
- package/src/components/data-grid/hooks/index.ts +28 -0
- package/src/components/data-grid/hooks/useColumnResize.ts +378 -0
- package/src/components/data-grid/hooks/useDataGridState.ts +346 -0
- package/src/components/data-grid/hooks/useKeyboardNavigation.ts +361 -0
- package/src/components/data-grid/index.ts +71 -0
- package/src/components/data-grid/types.ts +478 -0
- package/src/components/data-grid/utils/dataProcessing.ts +277 -0
- package/src/components/data-grid/utils/index.ts +12 -0
- package/src/components/date-picker.tsx +366 -0
- package/src/components/dropdown-menu.tsx +230 -0
- package/src/components/icon-button.tsx +157 -0
- package/src/components/input.tsx +40 -0
- package/src/components/label.tsx +37 -0
- package/src/components/loading-spinner.tsx +113 -0
- package/src/components/modal.tsx +207 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/progress.tsx +41 -0
- package/src/components/resizable-panel.tsx +434 -0
- package/src/components/resize-handle.tsx +187 -0
- package/src/components/select.tsx +160 -0
- package/src/components/separator.tsx +50 -0
- package/src/components/skeleton.tsx +37 -0
- package/src/components/switch.tsx +59 -0
- package/src/components/table.tsx +136 -0
- package/src/components/tabs.tsx +102 -0
- package/src/components/textarea.tsx +36 -0
- package/src/components/theme-picker.tsx +245 -0
- package/src/components/toaster.tsx +84 -0
- package/src/components/tooltip.tsx +199 -0
- package/src/index.ts +318 -0
- package/src/styles.css +96 -0
- package/src/tailwind-preset.ts +129 -0
- package/src/theme/index.ts +41 -0
- package/src/theme/presets.ts +502 -0
- package/src/theme/types.ts +164 -0
- package/src/theme/utils.ts +309 -0
- package/src/utils/cn.ts +14 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../utils/cn";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Button variant styles using class-variance-authority.
|
|
9
|
+
* Provides consistent button styling across the application.
|
|
10
|
+
*/
|
|
11
|
+
const buttonVariants = cva(
|
|
12
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
13
|
+
{
|
|
14
|
+
variants: {
|
|
15
|
+
variant: {
|
|
16
|
+
default:
|
|
17
|
+
"bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
|
18
|
+
primary: "bg-accent text-accent-foreground shadow hover:shadow-md",
|
|
19
|
+
destructive:
|
|
20
|
+
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 hover:shadow-md",
|
|
21
|
+
outline:
|
|
22
|
+
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
|
23
|
+
secondary:
|
|
24
|
+
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
|
25
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
26
|
+
link: "text-primary underline-offset-4 hover:underline",
|
|
27
|
+
},
|
|
28
|
+
size: {
|
|
29
|
+
default: "h-9 px-4 py-2",
|
|
30
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
31
|
+
lg: "h-10 rounded-md px-8",
|
|
32
|
+
icon: "h-9 w-9",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: {
|
|
36
|
+
variant: "default",
|
|
37
|
+
size: "default",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
export interface ButtonProps
|
|
43
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
44
|
+
VariantProps<typeof buttonVariants> {
|
|
45
|
+
/**
|
|
46
|
+
* If true, the button will render as its child element (Slot pattern).
|
|
47
|
+
* Useful for wrapping other components like links.
|
|
48
|
+
*/
|
|
49
|
+
asChild?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Button Component
|
|
54
|
+
*
|
|
55
|
+
* A versatile button component with multiple variants and sizes.
|
|
56
|
+
* Supports the Slot pattern for composing with other elements.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* <Button variant="primary">Click me</Button>
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* <Button variant="ghost" size="icon">
|
|
63
|
+
* <Icon />
|
|
64
|
+
* </Button>
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* <Button asChild>
|
|
68
|
+
* <a href="/link">Link Button</a>
|
|
69
|
+
* </Button>
|
|
70
|
+
*/
|
|
71
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
72
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
73
|
+
const Comp = asChild ? Slot : "button";
|
|
74
|
+
return (
|
|
75
|
+
<Comp
|
|
76
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
77
|
+
ref={ref}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
Button.displayName = "Button";
|
|
84
|
+
|
|
85
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calendar Component
|
|
3
|
+
*
|
|
4
|
+
* A styled calendar component built on react-day-picker.
|
|
5
|
+
* Matches the library's design system with proper theming.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Single date, range, and multiple selection modes
|
|
9
|
+
* - Keyboard navigation
|
|
10
|
+
* - Min/max date constraints
|
|
11
|
+
* - Disabled dates
|
|
12
|
+
* - Theme-aware styling
|
|
13
|
+
* - Month/year dropdown selectors for quick navigation
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import * as React from "react";
|
|
17
|
+
import { DayPicker, type DayPickerProps } from "react-day-picker";
|
|
18
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
19
|
+
import { cn } from "../utils/cn";
|
|
20
|
+
import { buttonVariants } from "./button";
|
|
21
|
+
import {
|
|
22
|
+
Select,
|
|
23
|
+
SelectContent,
|
|
24
|
+
SelectItem,
|
|
25
|
+
SelectTrigger,
|
|
26
|
+
SelectValue,
|
|
27
|
+
} from "./select";
|
|
28
|
+
|
|
29
|
+
export type CalendarProps = DayPickerProps;
|
|
30
|
+
|
|
31
|
+
const MONTHS = [
|
|
32
|
+
"January",
|
|
33
|
+
"February",
|
|
34
|
+
"March",
|
|
35
|
+
"April",
|
|
36
|
+
"May",
|
|
37
|
+
"June",
|
|
38
|
+
"July",
|
|
39
|
+
"August",
|
|
40
|
+
"September",
|
|
41
|
+
"October",
|
|
42
|
+
"November",
|
|
43
|
+
"December",
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Generate an array of years for the year selector
|
|
48
|
+
* Defaults to 100 years before and 20 years after current year
|
|
49
|
+
*/
|
|
50
|
+
function getYearRange(fromYear?: number, toYear?: number): number[] {
|
|
51
|
+
const currentYear = new Date().getFullYear();
|
|
52
|
+
const start = fromYear ?? currentYear - 100;
|
|
53
|
+
const end = toYear ?? currentYear + 20;
|
|
54
|
+
const years: number[] = [];
|
|
55
|
+
for (let year = start; year <= end; year++) {
|
|
56
|
+
years.push(year);
|
|
57
|
+
}
|
|
58
|
+
return years;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Calendar Component
|
|
63
|
+
*
|
|
64
|
+
* A fully styled calendar using react-day-picker with library design tokens.
|
|
65
|
+
*
|
|
66
|
+
* @example Single date selection
|
|
67
|
+
* ```tsx
|
|
68
|
+
* const [date, setDate] = useState<Date>();
|
|
69
|
+
* <Calendar mode="single" selected={date} onSelect={setDate} />
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @example Date range selection
|
|
73
|
+
* ```tsx
|
|
74
|
+
* const [range, setRange] = useState<DateRange>();
|
|
75
|
+
* <Calendar mode="range" selected={range} onSelect={setRange} />
|
|
76
|
+
* ```
|
|
77
|
+
*
|
|
78
|
+
* @example Quick navigation with month/year selectors
|
|
79
|
+
* ```tsx
|
|
80
|
+
* <Calendar
|
|
81
|
+
* mode="single"
|
|
82
|
+
* selected={date}
|
|
83
|
+
* onSelect={setDate}
|
|
84
|
+
* fromYear={2020}
|
|
85
|
+
* toYear={2030}
|
|
86
|
+
* />
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
function Calendar({
|
|
90
|
+
className,
|
|
91
|
+
classNames,
|
|
92
|
+
showOutsideDays = true,
|
|
93
|
+
month: controlledMonth,
|
|
94
|
+
defaultMonth,
|
|
95
|
+
onMonthChange,
|
|
96
|
+
...props
|
|
97
|
+
}: CalendarProps) {
|
|
98
|
+
const startMonth = props.startMonth ?? props.fromDate;
|
|
99
|
+
const endMonth = props.endMonth ?? props.toDate;
|
|
100
|
+
const fromYear = startMonth?.getFullYear();
|
|
101
|
+
const toYear = endMonth?.getFullYear();
|
|
102
|
+
const years = React.useMemo(() => getYearRange(fromYear, toYear), [fromYear, toYear]);
|
|
103
|
+
|
|
104
|
+
const [internalMonth, setInternalMonth] = React.useState<Date>(
|
|
105
|
+
defaultMonth ?? new Date()
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const displayedMonth = controlledMonth ?? internalMonth;
|
|
109
|
+
|
|
110
|
+
const handleMonthChange = React.useCallback(
|
|
111
|
+
(newMonth: Date) => {
|
|
112
|
+
if (controlledMonth === undefined) {
|
|
113
|
+
setInternalMonth(newMonth);
|
|
114
|
+
}
|
|
115
|
+
onMonthChange?.(newMonth);
|
|
116
|
+
},
|
|
117
|
+
[controlledMonth, onMonthChange]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<DayPicker
|
|
122
|
+
showOutsideDays={showOutsideDays}
|
|
123
|
+
month={displayedMonth}
|
|
124
|
+
onMonthChange={handleMonthChange}
|
|
125
|
+
className={cn("p-3", className)}
|
|
126
|
+
classNames={{
|
|
127
|
+
months: "flex flex-col sm:flex-row gap-4",
|
|
128
|
+
month: "flex flex-col gap-4",
|
|
129
|
+
month_caption: "flex justify-center pt-1 relative items-center h-7 px-10",
|
|
130
|
+
caption_label: "text-sm font-medium hidden",
|
|
131
|
+
nav: "flex items-center gap-1",
|
|
132
|
+
button_previous: cn(
|
|
133
|
+
buttonVariants({ variant: "outline" }),
|
|
134
|
+
"absolute left-1 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 z-10"
|
|
135
|
+
),
|
|
136
|
+
button_next: cn(
|
|
137
|
+
buttonVariants({ variant: "outline" }),
|
|
138
|
+
"absolute right-1 h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 z-10"
|
|
139
|
+
),
|
|
140
|
+
month_grid: "w-full border-collapse px-1",
|
|
141
|
+
weekdays: "flex",
|
|
142
|
+
weekday:
|
|
143
|
+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
|
144
|
+
week: "flex w-full mt-2",
|
|
145
|
+
day: cn(
|
|
146
|
+
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
|
147
|
+
props.mode === "range"
|
|
148
|
+
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
|
149
|
+
: "[&:has([aria-selected])]:rounded-md"
|
|
150
|
+
),
|
|
151
|
+
day_button: cn(
|
|
152
|
+
buttonVariants({ variant: "ghost" }),
|
|
153
|
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
|
154
|
+
),
|
|
155
|
+
range_start: "day-range-start rounded-l-md",
|
|
156
|
+
range_end: "day-range-end rounded-r-md",
|
|
157
|
+
selected:
|
|
158
|
+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
159
|
+
today: "bg-accent text-accent-foreground",
|
|
160
|
+
outside:
|
|
161
|
+
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
|
162
|
+
disabled: "text-muted-foreground opacity-50",
|
|
163
|
+
range_middle:
|
|
164
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
165
|
+
hidden: "invisible",
|
|
166
|
+
...classNames,
|
|
167
|
+
}}
|
|
168
|
+
components={{
|
|
169
|
+
Chevron: ({ orientation }) => {
|
|
170
|
+
const Icon = orientation === "left" ? ChevronLeft : ChevronRight;
|
|
171
|
+
return <Icon className="h-4 w-4" />;
|
|
172
|
+
},
|
|
173
|
+
MonthCaption: ({ calendarMonth }) => {
|
|
174
|
+
const month = calendarMonth.date.getMonth();
|
|
175
|
+
const year = calendarMonth.date.getFullYear();
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div className="flex items-center justify-center gap-1 px-10">
|
|
179
|
+
<Select
|
|
180
|
+
value={month.toString()}
|
|
181
|
+
onValueChange={(value) => {
|
|
182
|
+
const newDate = new Date(calendarMonth.date);
|
|
183
|
+
newDate.setMonth(parseInt(value, 10));
|
|
184
|
+
handleMonthChange(newDate);
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<SelectTrigger
|
|
188
|
+
className="h-7 w-[110px] text-sm font-medium border-none shadow-none hover:bg-accent focus:ring-0 px-2"
|
|
189
|
+
aria-label="Select month"
|
|
190
|
+
>
|
|
191
|
+
<SelectValue>{MONTHS[month]}</SelectValue>
|
|
192
|
+
</SelectTrigger>
|
|
193
|
+
<SelectContent>
|
|
194
|
+
{MONTHS.map((monthName, index) => (
|
|
195
|
+
<SelectItem key={monthName} value={index.toString()}>
|
|
196
|
+
{monthName}
|
|
197
|
+
</SelectItem>
|
|
198
|
+
))}
|
|
199
|
+
</SelectContent>
|
|
200
|
+
</Select>
|
|
201
|
+
|
|
202
|
+
<Select
|
|
203
|
+
value={year.toString()}
|
|
204
|
+
onValueChange={(value) => {
|
|
205
|
+
const newDate = new Date(calendarMonth.date);
|
|
206
|
+
newDate.setFullYear(parseInt(value, 10));
|
|
207
|
+
handleMonthChange(newDate);
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
<SelectTrigger
|
|
211
|
+
className="h-7 w-[70px] text-sm font-medium border-none shadow-none hover:bg-accent focus:ring-0 px-2"
|
|
212
|
+
aria-label="Select year"
|
|
213
|
+
>
|
|
214
|
+
<SelectValue>{year}</SelectValue>
|
|
215
|
+
</SelectTrigger>
|
|
216
|
+
<SelectContent className="max-h-[200px]">
|
|
217
|
+
{years.map((y) => (
|
|
218
|
+
<SelectItem key={y} value={y.toString()}>
|
|
219
|
+
{y}
|
|
220
|
+
</SelectItem>
|
|
221
|
+
))}
|
|
222
|
+
</SelectContent>
|
|
223
|
+
</Select>
|
|
224
|
+
</div>
|
|
225
|
+
);
|
|
226
|
+
},
|
|
227
|
+
}}
|
|
228
|
+
{...props}
|
|
229
|
+
/>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
Calendar.displayName = "Calendar";
|
|
234
|
+
|
|
235
|
+
export { Calendar };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils/cn";
|
|
4
|
+
|
|
5
|
+
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Card - Container component for grouped content
|
|
9
|
+
*/
|
|
10
|
+
const Card = React.forwardRef<HTMLDivElement, CardProps>(
|
|
11
|
+
({ className, ...props }, ref) => (
|
|
12
|
+
<div
|
|
13
|
+
ref={ref}
|
|
14
|
+
className={cn(
|
|
15
|
+
"rounded-xl border bg-card text-card-foreground shadow",
|
|
16
|
+
className
|
|
17
|
+
)}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
Card.displayName = "Card";
|
|
23
|
+
|
|
24
|
+
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
25
|
+
|
|
26
|
+
const CardHeader = React.forwardRef<HTMLDivElement, CardHeaderProps>(
|
|
27
|
+
({ className, ...props }, ref) => (
|
|
28
|
+
<div
|
|
29
|
+
ref={ref}
|
|
30
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
);
|
|
35
|
+
CardHeader.displayName = "CardHeader";
|
|
36
|
+
|
|
37
|
+
export interface CardTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
38
|
+
|
|
39
|
+
const CardTitle = React.forwardRef<HTMLDivElement, CardTitleProps>(
|
|
40
|
+
({ className, ...props }, ref) => (
|
|
41
|
+
<div
|
|
42
|
+
ref={ref}
|
|
43
|
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
CardTitle.displayName = "CardTitle";
|
|
49
|
+
|
|
50
|
+
export interface CardDescriptionProps
|
|
51
|
+
extends React.HTMLAttributes<HTMLDivElement> {}
|
|
52
|
+
|
|
53
|
+
const CardDescription = React.forwardRef<HTMLDivElement, CardDescriptionProps>(
|
|
54
|
+
({ className, ...props }, ref) => (
|
|
55
|
+
<div
|
|
56
|
+
ref={ref}
|
|
57
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
58
|
+
{...props}
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
);
|
|
62
|
+
CardDescription.displayName = "CardDescription";
|
|
63
|
+
|
|
64
|
+
export interface CardContentProps
|
|
65
|
+
extends React.HTMLAttributes<HTMLDivElement> {}
|
|
66
|
+
|
|
67
|
+
const CardContent = React.forwardRef<HTMLDivElement, CardContentProps>(
|
|
68
|
+
({ className, ...props }, ref) => (
|
|
69
|
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
CardContent.displayName = "CardContent";
|
|
73
|
+
|
|
74
|
+
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
75
|
+
|
|
76
|
+
const CardFooter = React.forwardRef<HTMLDivElement, CardFooterProps>(
|
|
77
|
+
({ className, ...props }, ref) => (
|
|
78
|
+
<div
|
|
79
|
+
ref={ref}
|
|
80
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
);
|
|
85
|
+
CardFooter.displayName = "CardFooter";
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
Card,
|
|
89
|
+
CardHeader,
|
|
90
|
+
CardFooter,
|
|
91
|
+
CardTitle,
|
|
92
|
+
CardDescription,
|
|
93
|
+
CardContent,
|
|
94
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
3
|
+
import { Check } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../utils/cn";
|
|
6
|
+
|
|
7
|
+
export interface CheckboxProps
|
|
8
|
+
extends React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> {
|
|
9
|
+
/**
|
|
10
|
+
* Whether to show a visible border around the checkbox.
|
|
11
|
+
* @default true
|
|
12
|
+
*/
|
|
13
|
+
showBorder?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checkbox Component
|
|
18
|
+
*
|
|
19
|
+
* An accessible checkbox input built on Radix UI primitives.
|
|
20
|
+
* Supports controlled and uncontrolled modes.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* <Checkbox id="terms" />
|
|
24
|
+
* <Label htmlFor="terms">Accept terms</Label>
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <Checkbox checked={isChecked} onCheckedChange={setIsChecked} />
|
|
28
|
+
*/
|
|
29
|
+
const Checkbox = React.forwardRef<
|
|
30
|
+
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
31
|
+
CheckboxProps
|
|
32
|
+
>(({ className, showBorder = true, ...props }, ref) => (
|
|
33
|
+
<CheckboxPrimitive.Root
|
|
34
|
+
ref={ref}
|
|
35
|
+
className={cn(
|
|
36
|
+
// Base styles
|
|
37
|
+
"grid place-content-center peer shrink-0",
|
|
38
|
+
"h-4 w-4 rounded-sm",
|
|
39
|
+
"transition-colors duration-150",
|
|
40
|
+
// Focus styles
|
|
41
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
42
|
+
// Disabled styles
|
|
43
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
44
|
+
// Border styles (conditional)
|
|
45
|
+
showBorder
|
|
46
|
+
? [
|
|
47
|
+
// Visible border for better contrast
|
|
48
|
+
"border-2 border-foreground/40",
|
|
49
|
+
// Hover state
|
|
50
|
+
"hover:border-foreground/60",
|
|
51
|
+
// Checked state - use accent background with contrasting border
|
|
52
|
+
"data-[state=checked]:bg-accent data-[state=checked]:border-accent",
|
|
53
|
+
]
|
|
54
|
+
: [
|
|
55
|
+
// No border variant (for inline uses)
|
|
56
|
+
"border border-transparent",
|
|
57
|
+
// Checked state with subtle background
|
|
58
|
+
"data-[state=checked]:bg-accent/80",
|
|
59
|
+
],
|
|
60
|
+
className
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
<CheckboxPrimitive.Indicator
|
|
65
|
+
className={cn(
|
|
66
|
+
"grid place-content-center",
|
|
67
|
+
// Use foreground color that contrasts with accent
|
|
68
|
+
"text-accent-foreground"
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
<Check className="h-3.5 w-3.5 stroke-[3]" />
|
|
72
|
+
</CheckboxPrimitive.Indicator>
|
|
73
|
+
</CheckboxPrimitive.Root>
|
|
74
|
+
));
|
|
75
|
+
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
|
76
|
+
|
|
77
|
+
export { Checkbox };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../utils/cn";
|
|
4
|
+
|
|
5
|
+
export interface ChipProps {
|
|
6
|
+
/**
|
|
7
|
+
* Chip label text
|
|
8
|
+
*/
|
|
9
|
+
label: string;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Click handler
|
|
13
|
+
*/
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Whether the chip is disabled
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Additional class names
|
|
23
|
+
*/
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Chip component
|
|
29
|
+
*
|
|
30
|
+
* A clickable pill-shaped component for tags, filters, or suggested actions.
|
|
31
|
+
* Uses the theme chip colors.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* <Chip label="React" onClick={() => console.log('clicked')} />
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* <Chip label="Disabled" disabled />
|
|
38
|
+
*/
|
|
39
|
+
export function Chip({
|
|
40
|
+
label,
|
|
41
|
+
onClick,
|
|
42
|
+
disabled = false,
|
|
43
|
+
className,
|
|
44
|
+
}: ChipProps) {
|
|
45
|
+
const isClickable = !!onClick && !disabled;
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
onClick={onClick}
|
|
50
|
+
disabled={!isClickable}
|
|
51
|
+
className={cn(
|
|
52
|
+
"inline-flex items-center justify-center",
|
|
53
|
+
"px-4 py-2 rounded-full",
|
|
54
|
+
"text-sm font-medium",
|
|
55
|
+
"transition-all duration-150",
|
|
56
|
+
// Default styling with chip colors
|
|
57
|
+
"bg-chip text-chip-foreground",
|
|
58
|
+
"border border-chip-foreground/20",
|
|
59
|
+
// Clickable state
|
|
60
|
+
isClickable && [
|
|
61
|
+
"cursor-pointer",
|
|
62
|
+
"hover:bg-accent hover:text-accent-foreground",
|
|
63
|
+
"hover:border-accent",
|
|
64
|
+
"hover:shadow-md",
|
|
65
|
+
],
|
|
66
|
+
// Non-clickable state
|
|
67
|
+
!isClickable && "cursor-default",
|
|
68
|
+
// Disabled state
|
|
69
|
+
disabled && "opacity-50 cursor-not-allowed",
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
type="button"
|
|
73
|
+
>
|
|
74
|
+
{label}
|
|
75
|
+
</button>
|
|
76
|
+
);
|
|
77
|
+
}
|