Skip to content

Commit 5b9bb4f

Browse files
feat: Allow passing className props to shadcn custom widgets (rjsf-team#4727)
* chore: update readme for image showcase and better theming alternative * feat: allow className props to be passed for custom tailwind class styling on widget * test: update test snapshots to cover for missing class on renders for updated elements * chore: Update changelog with new changes * Update CHANGELOG.md --------- Co-authored-by: Heath C <[email protected]>
1 parent 53df6d0 commit 5b9bb4f

File tree

12 files changed

+67
-28
lines changed

12 files changed

+67
-28
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@ it according to semantic versioning. For example, if your PR adds a breaking cha
1515
should change the heading of the (upcoming) version to include a major version bump.
1616
1717
-->
18+
# 6.0.0-beta.15
19+
20+
## @rjsf/shadcn
21+
22+
- Update `README.md` with picture of the theme!
23+
- Allow passing `className` props to `AddButton`, `BaseInputTemplate`, `CheckboxWidget`, `CheckboxesWidget`, `RadioWidget`, `SelectWidget`, `SubmitButton`, `TextareaWidget` for extra Tailwind CSS customization through `ui:className`
24+
1825
# 6.0.0-beta.14
1926

2027
## @rjsf/core

packages/shadcn/README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
<a href="https://github.com/rjsf-team/react-jsonschema-form/issues">Request Feature</a>
2121
</p>
2222

23+
24+
<p align="center">
25+
<img src="https://github.com/tuanphung2308/rjsf-shadcn-css/blob/main/shadcn-demo.png?raw=true" alt="Logo" width="720" height="240">
26+
</p>
27+
2328
<!-- TABLE OF CONTENTS -->
2429

2530
## Table of Contents
@@ -134,8 +139,7 @@ Supported colors are:
134139
#### Coloring
135140

136141
- Generate a theme from [official shadCN site](https://ui.shadcn.com/themes)
137-
or [zippy starter's shadcn/ui theme generator](https://zippystarter.com/tools/shadcn-ui-theme-generator)
138-
or [Railly](https://customizer.railly.dev/)
142+
or [tweakcn](https://tweakcn.com/editor/theme)
139143
- Navigate to shadcn/css, create a new file called [your-theme].css
140144
- Replace the base layer code with your new color
141145
- Follow the next section to build your CSS file

packages/shadcn/src/AddButton/AddButton.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,25 @@ import { FormContextType, IconButtonProps, RJSFSchema, StrictRJSFSchema, Transla
22
import { PlusCircle } from 'lucide-react';
33

44
import { Button } from '../components/ui/button';
5+
import { cn } from '../lib/utils';
56

67
/**
78
* A button component for adding new items in a form
9+
* @param uiSchema - The UI schema for the form, which can include custom properties
10+
* @param registry - The registry object containing the form's configuration and utilities
11+
* @param className - Allow custom class names to be passed for Tailwind CSS styling
812
* @param props - The component properties
913
*/
1014
export default function AddButton<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>({
1115
uiSchema,
1216
registry,
17+
className,
1318
...props
1419
}: IconButtonProps<T, S, F>) {
1520
const { translateString } = registry;
1621
return (
1722
<div className='p-0 m-0'>
18-
<Button {...props} className='w-fit gap-2' variant='outline' type='button'>
23+
<Button {...props} className={cn('w-fit gap-2', className)} variant='outline' type='button'>
1924
<PlusCircle size={16} /> {translateString(TranslatableString.AddItemButton)}
2025
</Button>
2126
</div>

packages/shadcn/src/BaseInputTemplate/BaseInputTemplate.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default function BaseInputTemplate<
4040
rawErrors = [],
4141
children,
4242
extraProps,
43+
className,
4344
}: BaseInputTemplateProps<T, S, F>) {
4445
const inputProps = {
4546
...extraProps,
@@ -61,7 +62,7 @@ export default function BaseInputTemplate<
6162
required={required}
6263
disabled={disabled}
6364
readOnly={readonly}
64-
className={cn({ 'border-destructive focus-visible:ring-0': rawErrors.length > 0 })}
65+
className={cn({ 'border-destructive focus-visible:ring-0': rawErrors.length > 0 }, className)}
6566
list={schema.examples ? examplesId<T>(id) : undefined}
6667
{...inputProps}
6768
value={value || value === 0 ? value : ''}

packages/shadcn/src/CheckboxWidget/CheckboxWidget.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default function CheckboxWidget<
3737
onFocus,
3838
registry,
3939
uiSchema,
40+
className,
4041
} = props;
4142
// Because an unchecked checkbox will cause html5 validation to fail, only add
4243
// the "required" attribute if the field value must be "true", due to the
@@ -78,6 +79,7 @@ export default function CheckboxWidget<
7879
onCheckedChange={_onChange}
7980
onBlur={_onBlur}
8081
onFocus={_onFocus}
82+
className={className}
8183
/>
8284
<Label className='leading-tight' htmlFor={id}>
8385
{labelValue(label, hideLabel || !label)}

packages/shadcn/src/CheckboxesWidget/CheckboxesWidget.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,19 @@ export default function CheckboxesWidget<
2525
T = any,
2626
S extends StrictRJSFSchema = RJSFSchema,
2727
F extends FormContextType = any,
28-
>({ id, disabled, options, value, autofocus, readonly, required, onChange, onBlur, onFocus }: WidgetProps<T, S, F>) {
28+
>({
29+
id,
30+
disabled,
31+
options,
32+
value,
33+
autofocus,
34+
readonly,
35+
required,
36+
onChange,
37+
onBlur,
38+
onFocus,
39+
className,
40+
}: WidgetProps<T, S, F>) {
2941
const { enumOptions, enumDisabled, inline, emptyValue } = options;
3042
const checkboxesValues = Array.isArray(value) ? value : [value];
3143

@@ -56,6 +68,7 @@ export default function CheckboxesWidget<
5668
onChange(enumOptionsDeselectValue<S>(index, checkboxesValues, enumOptions));
5769
}
5870
}}
71+
className={className}
5972
checked={checked}
6073
autoFocus={autofocus && index === 0}
6174
onBlur={_onBlur}

packages/shadcn/src/RadioWidget/RadioWidget.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export default function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSc
2929
onChange,
3030
onBlur,
3131
onFocus,
32+
className,
3233
}: WidgetProps<T, S, F>) {
3334
const { enumOptions, enumDisabled, emptyValue } = options;
3435

@@ -53,7 +54,7 @@ export default function RadioWidget<T = any, S extends StrictRJSFSchema = RJSFSc
5354
onFocus={_onFocus}
5455
aria-describedby={ariaDescribedByIds<T>(id)}
5556
orientation={inline ? 'horizontal' : 'vertical'}
56-
className={cn('flex flex-wrap', { 'flex-col': !inline })}
57+
className={cn('flex flex-wrap', { 'flex-col': !inline }, className)}
5758
>
5859
{Array.isArray(enumOptions) &&
5960
enumOptions.map((option, index) => {

packages/shadcn/src/SelectWidget/SelectWidget.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default function SelectWidget<
3636
defaultValue,
3737
placeholder,
3838
rawErrors = [],
39+
className,
3940
}: WidgetProps<T, S, F>) {
4041
const { enumOptions, enumDisabled, emptyValue: optEmptyValue } = options;
4142

@@ -54,6 +55,8 @@ export default function SelectWidget<
5455
disabled: Array.isArray(enumDisabled) && enumDisabled.includes(value),
5556
}));
5657

58+
const cnClassName = cn({ 'border-destructive': rawErrors.length > 0 }, className);
59+
5760
return (
5861
<div className='p-0.5'>
5962
{!multiple ? (
@@ -67,7 +70,7 @@ export default function SelectWidget<
6770
disabled={disabled || readonly}
6871
required={required}
6972
placeholder={placeholder}
70-
className={cn({ 'border-destructive': rawErrors.length > 0 })}
73+
className={cnClassName}
7174
onFocus={_onFancyFocus}
7275
onBlur={_onFancyBlur}
7376
ariaDescribedby={ariaDescribedByIds<T>(id)}
@@ -78,7 +81,7 @@ export default function SelectWidget<
7881
autoFocus={autofocus}
7982
disabled={disabled || readonly}
8083
multiple
81-
className={rawErrors.length > 0 ? 'border-destructive' : ''}
84+
className={cnClassName}
8285
items={items}
8386
selected={value}
8487
onValueChange={(values) => {

packages/shadcn/src/SubmitButton/SubmitButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { FormContextType, getSubmitButtonOptions, RJSFSchema, StrictRJSFSchema, SubmitButtonProps } from '@rjsf/utils';
22

33
import { Button } from '../components/ui/button';
4+
import { cn } from '../lib/utils';
45

56
/** The `SubmitButton` renders a button that represent the `Submit` action on a form
67
*/
@@ -13,7 +14,7 @@ export default function SubmitButton<T = any, S extends StrictRJSFSchema = RJSFS
1314
}
1415
return (
1516
<div>
16-
<Button type='submit' {...submitButtonProps} className='my-2'>
17+
<Button type='submit' {...submitButtonProps} className={cn('my-2', submitButtonProps?.className)}>
1718
{submitText}
1819
</Button>
1920
</div>

packages/shadcn/src/TextareaWidget/TextareaWidget.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default function TextareaWidget<
3131
onFocus,
3232
onChange,
3333
options,
34+
className,
3435
}: CustomWidgetProps<T, S, F>) {
3536
const _onChange = ({ target: { value } }: ChangeEvent<HTMLTextAreaElement>) =>
3637
onChange(value === '' ? options.emptyValue : value);
@@ -53,6 +54,7 @@ export default function TextareaWidget<
5354
onBlur={_onBlur}
5455
onFocus={_onFocus}
5556
aria-describedby={ariaDescribedByIds<T>(id)}
57+
className={className}
5658
/>
5759
</div>
5860
);

0 commit comments

Comments
 (0)