Skip to content

Commit 692377f

Browse files
committed
feat(components): add className prop support for all UI components
Add className prop to component definitions and implement className handling in preview component to allow custom styling. Remove deprecated collapsible prop from Accordion as it's handled internally by Radix.
1 parent e0dca64 commit 692377f

File tree

2 files changed

+100
-20
lines changed

2 files changed

+100
-20
lines changed

src/components/features/component-preview.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ export function ComponentPreview() {
161161
switch (name) {
162162
case 'accordion': {
163163
const type = (validProps.type as string) ?? 'single';
164-
const collapsible = (validProps.collapsible as boolean) ?? true;
164+
// Avoid passing `collapsible` to DOM to prevent React warning; Radix handles it when type="single"
165165
return (
166-
<Accordion type={type as 'single' | 'multiple'} collapsible={collapsible} className="w-full max-w-md">
166+
<Accordion type={type as 'single' | 'multiple'} className={(validProps.className as string) ?? 'w-full max-w-md'}>
167167
<AccordionItem value="item-1">
168168
<AccordionTrigger>Is it accessible?</AccordionTrigger>
169169
<AccordionContent>
@@ -196,7 +196,7 @@ export function ComponentPreview() {
196196
}
197197
case 'table': {
198198
return (
199-
<Table className="w-full max-w-2xl">
199+
<Table className={(validProps.className as string) ?? 'w-full max-w-2xl'}>
200200
<TableHeader>
201201
<TableRow>
202202
<TableHead>Invoice</TableHead>
@@ -225,7 +225,7 @@ export function ComponentPreview() {
225225
case 'radio-group': {
226226
const defaultValue = (validProps.defaultValue as string) ?? 'option-one';
227227
return (
228-
<RadioGroup defaultValue={defaultValue} className="space-y-2">
228+
<RadioGroup defaultValue={defaultValue} className={(validProps.className as string) ?? 'space-y-2'}>
229229
<div className="flex items-center space-x-2">
230230
<RadioGroupItem value="option-one" id="option-one" />
231231
<Label htmlFor="option-one">Option One</Label>
@@ -242,7 +242,7 @@ export function ComponentPreview() {
242242
const disabled = (validProps.disabled as boolean) ?? false;
243243
return (
244244
<Select disabled={disabled}>
245-
<SelectTrigger className="w-[240px]">
245+
<SelectTrigger className={(validProps.className as string) ?? 'w-[240px]'}>
246246
<SelectValue placeholder={placeholder} />
247247
</SelectTrigger>
248248
<SelectContent>
@@ -256,7 +256,7 @@ export function ComponentPreview() {
256256
case 'tabs': {
257257
const defaultValue = (validProps.defaultValue as string) ?? 'tab1';
258258
return (
259-
<Tabs defaultValue={defaultValue} className="w-[400px]">
259+
<Tabs defaultValue={defaultValue} className={(validProps.className as string) ?? 'w-[400px]'}>
260260
<TabsList>
261261
<TabsTrigger value="tab1">Tab 1</TabsTrigger>
262262
<TabsTrigger value="tab2">Tab 2</TabsTrigger>
@@ -279,7 +279,7 @@ export function ComponentPreview() {
279279
return (
280280
<div className="w-full max-w-md">
281281
<p className="mb-2">Section A</p>
282-
<Separator orientation={orientation as 'horizontal' | 'vertical'} />
282+
<Separator orientation={orientation as 'horizontal' | 'vertical'} className={(validProps.className as string) ?? ''} />
283283
<p className="mt-2">Section B</p>
284284
</div>
285285
);
@@ -295,7 +295,7 @@ export function ComponentPreview() {
295295
case 'resizable': {
296296
const leftSize = (validProps.defaultSize as number) ?? 50;
297297
return (
298-
<ResizablePanelGroup direction="horizontal" className="w-[500px]">
298+
<ResizablePanelGroup direction="horizontal" className={(validProps.className as string) ?? 'w-[500px]'}>
299299
<ResizablePanel defaultSize={leftSize}>
300300
<div className="flex h-[200px] items-center justify-center p-6">
301301
<span className="font-semibold">Panel 1</span>
@@ -326,7 +326,7 @@ export function ComponentPreview() {
326326
case 'alert': {
327327
const variant = (validProps.variant as string) ?? 'default';
328328
return (
329-
<Alert variant={variant as 'default' | 'destructive'} className="w-full max-w-md">
329+
<Alert variant={variant as 'default' | 'destructive'} className={(validProps.className as string) ?? 'w-full max-w-md'}>
330330
<AlertTitle>Heads up!</AlertTitle>
331331
<AlertDescription>
332332
You can add components to your app using the cli.
@@ -337,29 +337,29 @@ export function ComponentPreview() {
337337
case 'checkbox': {
338338
const checked = (validProps.checked as boolean) ?? false;
339339
const disabled = (validProps.disabled as boolean) ?? false;
340-
return <Checkbox checked={checked} disabled={disabled} />;
340+
return <Checkbox checked={checked} disabled={disabled} className={(validProps.className as string) ?? ''} />;
341341
}
342342
case 'switch': {
343343
const checked = (validProps.checked as boolean) ?? false;
344344
const disabled = (validProps.disabled as boolean) ?? false;
345-
return <Switch checked={checked} disabled={disabled} />;
345+
return <Switch checked={checked} disabled={disabled} className={(validProps.className as string) ?? ''} />;
346346
}
347347
case 'textarea': {
348348
const placeholder = (validProps.placeholder as string) ?? 'Enter your message...';
349349
const disabled = (validProps.disabled as boolean) ?? false;
350350
const rows = (validProps.rows as number) ?? 4;
351-
return <Textarea placeholder={placeholder} disabled={disabled} rows={rows} className="w-[350px]" />;
351+
return <Textarea placeholder={placeholder} disabled={disabled} rows={rows} className={(validProps.className as string) ?? 'w-[350px]'} />;
352352
}
353353
case 'input': {
354354
const type = (validProps.type as string) ?? 'text';
355355
const placeholder = (validProps.placeholder as string) ?? 'Enter text...';
356356
const disabled = (validProps.disabled as boolean) ?? false;
357-
return <Input type={type} placeholder={placeholder} disabled={disabled} className="w-[280px]" />;
357+
return <Input type={type} placeholder={placeholder} disabled={disabled} className={(validProps.className as string) ?? 'w-[280px]'} />;
358358
}
359359
case 'badge': {
360360
const variant = (validProps.variant as string) ?? 'default';
361361
const children = (validProps.children as string) ?? 'Badge';
362-
return <BadgeComponent variant={variant as 'default' | 'secondary' | 'destructive' | 'outline'}>{children}</BadgeComponent>;
362+
return <BadgeComponent variant={variant as 'default' | 'secondary' | 'destructive' | 'outline'} className={(validProps.className as string) ?? ''}>{children}</BadgeComponent>;
363363
}
364364
case 'button': {
365365
const variant = (validProps.variant as string) ?? 'default';
@@ -371,6 +371,7 @@ export function ComponentPreview() {
371371
variant={variant as import('class-variance-authority').VariantProps<typeof buttonVariants>['variant']}
372372
size={size as import('class-variance-authority').VariantProps<typeof buttonVariants>['size']}
373373
disabled={disabled}
374+
className={(validProps.className as string) ?? ''}
374375
>
375376
{children}
376377
</Button>

src/lib/component-data.ts

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
1111
importPath: '@/components/ui/button',
1212
dependencies: [],
1313
props: [
14+
{
15+
name: 'className',
16+
type: 'string',
17+
defaultValue: '',
18+
description: 'Additional CSS classes for styling (colors, borders, radius, etc.)'
19+
},
1420
{
1521
name: 'variant',
1622
type: 'enum',
@@ -55,6 +61,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
5561
importPath: '@/components/ui/alert',
5662
dependencies: [],
5763
props: [
64+
{
65+
name: 'className',
66+
type: 'string',
67+
defaultValue: '',
68+
description: 'Additional CSS classes for styling (colors, borders, spacing)'
69+
},
5870
{
5971
name: 'variant',
6072
type: 'enum',
@@ -82,6 +94,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
8294
importPath: '@/components/ui/dialog',
8395
dependencies: [],
8496
props: [
97+
{
98+
name: 'className',
99+
type: 'string',
100+
defaultValue: '',
101+
description: 'Additional CSS classes applied to DialogContent'
102+
},
85103
{
86104
name: 'open',
87105
type: 'boolean',
@@ -110,7 +128,14 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
110128
category: 'data',
111129
importPath: '@/components/ui/table',
112130
dependencies: [],
113-
props: [],
131+
props: [
132+
{
133+
name: 'className',
134+
type: 'string',
135+
defaultValue: '',
136+
description: 'Additional CSS classes for styling the table'
137+
}
138+
],
114139
variants: [
115140
{ name: 'Default', props: {} }
116141
],
@@ -149,17 +174,17 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
149174
options: ['single', 'multiple']
150175
},
151176
{
152-
name: 'collapsible',
153-
type: 'boolean',
154-
defaultValue: true,
155-
description: 'Whether the accordion items can be collapsed'
177+
name: 'className',
178+
type: 'string',
179+
defaultValue: '',
180+
description: 'Additional CSS classes for styling'
156181
}
157182
],
158183
variants: [
159184
{ name: 'Single', props: { type: 'single' } },
160185
{ name: 'Multiple', props: { type: 'multiple' } }
161186
],
162-
demoCode: `<Accordion type="single" collapsible>
187+
demoCode: `<Accordion type="single">
163188
<AccordionItem value="item-1">
164189
<AccordionTrigger>Is it accessible?</AccordionTrigger>
165190
<AccordionContent>
@@ -176,6 +201,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
176201
importPath: '@/components/ui/checkbox',
177202
dependencies: [],
178203
props: [
204+
{
205+
name: 'className',
206+
type: 'string',
207+
defaultValue: '',
208+
description: 'Additional CSS classes for styling'
209+
},
179210
{
180211
name: 'checked',
181212
type: 'boolean',
@@ -204,6 +235,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
204235
importPath: '@/components/ui/radio-group',
205236
dependencies: [],
206237
props: [
238+
{
239+
name: 'className',
240+
type: 'string',
241+
defaultValue: '',
242+
description: 'Additional CSS classes for styling'
243+
},
207244
{
208245
name: 'defaultValue',
209246
type: 'string',
@@ -343,6 +380,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
343380
importPath: '@/components/ui/badge',
344381
dependencies: [],
345382
props: [
383+
{
384+
name: 'className',
385+
type: 'string',
386+
defaultValue: '',
387+
description: 'Additional CSS classes for styling'
388+
},
346389
{
347390
name: 'variant',
348391
type: 'enum',
@@ -373,6 +416,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
373416
importPath: '@/components/ui/switch',
374417
dependencies: [],
375418
props: [
419+
{
420+
name: 'className',
421+
type: 'string',
422+
defaultValue: '',
423+
description: 'Additional CSS classes for styling'
424+
},
376425
{
377426
name: 'checked',
378427
type: 'boolean',
@@ -401,6 +450,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
401450
importPath: '@/components/ui/textarea',
402451
dependencies: [],
403452
props: [
453+
{
454+
name: 'className',
455+
type: 'string',
456+
defaultValue: '',
457+
description: 'Additional CSS classes for styling'
458+
},
404459
{
405460
name: 'placeholder',
406461
type: 'string',
@@ -434,6 +489,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
434489
importPath: '@/components/ui/select',
435490
dependencies: [],
436491
props: [
492+
{
493+
name: 'className',
494+
type: 'string',
495+
defaultValue: '',
496+
description: 'Additional CSS classes applied to SelectTrigger'
497+
},
437498
{
438499
name: 'placeholder',
439500
type: 'string',
@@ -470,6 +531,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
470531
importPath: '@/components/ui/tabs',
471532
dependencies: [],
472533
props: [
534+
{
535+
name: 'className',
536+
type: 'string',
537+
defaultValue: '',
538+
description: 'Additional CSS classes for styling the root'
539+
},
473540
{
474541
name: 'defaultValue',
475542
type: 'string',
@@ -505,6 +572,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
505572
importPath: '@/components/ui/separator',
506573
dependencies: [],
507574
props: [
575+
{
576+
name: 'className',
577+
type: 'string',
578+
defaultValue: '',
579+
description: 'Additional CSS classes for color, thickness, spacing'
580+
},
508581
{
509582
name: 'orientation',
510583
type: 'enum',
@@ -549,6 +622,12 @@ export const getAvailableComponents = async (): Promise<ShadcnComponent[]> => {
549622
importPath: '@/components/ui/resizable',
550623
dependencies: [],
551624
props: [
625+
{
626+
name: 'className',
627+
type: 'string',
628+
defaultValue: '',
629+
description: 'Additional CSS classes applied to the panel group'
630+
},
552631
{
553632
name: 'defaultSize',
554633
type: 'number',

0 commit comments

Comments
 (0)