Skip to content

Commit 05c934a

Browse files
emilchevarluders
andauthored
feat(theme): add theme support for Checkbox, Radio and ToggleSwitch (#551)
Co-authored-by: Ricardo Lüders <[email protected]>
1 parent a62e84f commit 05c934a

File tree

7 files changed

+171
-19
lines changed

7 files changed

+171
-19
lines changed

src/lib/components/Checkbox/Checkbox.spec.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { render } from '@testing-library/react';
1+
import { render, screen } from '@testing-library/react';
22
import { describe, expect, it } from 'vitest';
3+
import { Flowbite } from '../Flowbite';
34
import { Checkbox } from './Checkbox';
45

56
describe.concurrent('Components / Checkbox', () => {
@@ -10,4 +11,25 @@ describe.concurrent('Components / Checkbox', () => {
1011
expect(checkbox).toBeInTheDocument();
1112
});
1213
});
14+
15+
describe('Theme', () => {
16+
it('should use custom `base` classes', () => {
17+
const theme = {
18+
checkbox: {
19+
root: {
20+
base: 'bg-yellow-400 dark:bg-yellow-40',
21+
},
22+
},
23+
};
24+
render(
25+
<Flowbite theme={{ theme }}>
26+
<Checkbox />
27+
</Flowbite>,
28+
);
29+
30+
expect(checkbox()).toHaveClass('bg-yellow-400 dark:bg-yellow-40');
31+
});
32+
});
1333
});
34+
35+
const checkbox = () => screen.getByRole('checkbox');

src/lib/components/Checkbox/Checkbox.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { mergeDeep } from '../../helpers/mergeDeep';
66
import { useTheme } from '../Flowbite/ThemeContext';
77

88
export interface FlowbiteCheckboxTheme {
9+
root: FlowbiteCheckboxRootTheme;
10+
}
11+
export interface FlowbiteCheckboxRootTheme {
912
base: string;
1013
}
1114

@@ -17,7 +20,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
1720
({ theme: customTheme = {}, className, ...props }, ref) => {
1821
const theme = mergeDeep(useTheme().theme.checkbox, customTheme);
1922

20-
return <input ref={ref} className={classNames(theme.base, className)} type="checkbox" {...props} />;
23+
return <input ref={ref} className={classNames(theme.root.base, className)} type="checkbox" {...props} />;
2124
},
2225
);
2326

src/lib/components/Radio/Radio.spec.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { render } from '@testing-library/react';
1+
import { render, screen } from '@testing-library/react';
22
import { describe, expect, it } from 'vitest';
3+
import { Flowbite } from '../Flowbite';
34
import { Radio } from './Radio';
45

56
describe.concurrent('Components / Radio', () => {
@@ -10,4 +11,25 @@ describe.concurrent('Components / Radio', () => {
1011
expect(radio).toBeInTheDocument();
1112
});
1213
});
14+
15+
describe('Theme', () => {
16+
it('should use custom `base` classes', () => {
17+
const theme = {
18+
radio: {
19+
root: {
20+
base: 'bg-yellow-400 dark:bg-yellow-40',
21+
},
22+
},
23+
};
24+
render(
25+
<Flowbite theme={{ theme }}>
26+
<Radio />
27+
</Flowbite>,
28+
);
29+
30+
expect(radio()).toHaveClass('bg-yellow-400 dark:bg-yellow-40');
31+
});
32+
});
1333
});
34+
35+
const radio = () => screen.getByRole('radio');

src/lib/components/Radio/Radio.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { mergeDeep } from '../../helpers/mergeDeep';
66
import { useTheme } from '../Flowbite/ThemeContext';
77

88
export interface FlowbiteRadioTheme {
9+
root: FlowbiteRadioRootTheme;
10+
}
11+
export interface FlowbiteRadioRootTheme {
912
base: string;
1013
}
1114

@@ -17,7 +20,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
1720
({ theme: customTheme = {}, className, ...props }, ref) => {
1821
const theme = mergeDeep(useTheme().theme.radio, customTheme);
1922

20-
return <input ref={ref} className={classNames(theme.base, className)} type="radio" {...props} />;
23+
return <input ref={ref} className={classNames(theme.root.base, className)} type="radio" {...props} />;
2124
},
2225
);
2326

src/lib/components/ToggleSwitch/ToggleSwitch.spec.tsx

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import { FC, useState } from 'react';
44
import { describe, expect, it, vi } from 'vitest';
5+
import { Flowbite } from '../Flowbite';
56
import { TextInput } from '../TextInput';
67
import { ToggleSwitch } from './ToggleSwitch';
78

@@ -127,6 +128,94 @@ describe('Components / Toggle switch', () => {
127128
expect(toggleSwitch()).toHaveAttribute('type', 'submit');
128129
});
129130
});
131+
132+
describe('Theme', () => {
133+
it('should use `base` classes', () => {
134+
const theme = {
135+
toggleSwitch: {
136+
root: {
137+
base: 'text-blue-100',
138+
},
139+
},
140+
};
141+
render(
142+
<Flowbite theme={{ theme }}>
143+
<ToggleSwitch checked={false} label="Enable" onChange={console.log} type="submit" />
144+
</Flowbite>,
145+
);
146+
147+
expect(toggleSwitch()).toHaveClass('text-blue-100');
148+
});
149+
150+
it('should use `active` classes', () => {
151+
const theme = {
152+
toggleSwitch: {
153+
root: {
154+
active: {
155+
off: 'text-blue-200',
156+
on: 'text-blue-300',
157+
},
158+
},
159+
},
160+
};
161+
render(
162+
<Flowbite theme={{ theme }}>
163+
<ToggleSwitch checked={false} label="Enable" onChange={console.log} type="submit" />
164+
<ToggleSwitch disabled checked={false} label="Enable" onChange={console.log} type="submit" />
165+
</Flowbite>,
166+
);
167+
const activeToggleSwitch = toggleSwitches()[0];
168+
const disabledToggleSwitch = toggleSwitches()[1];
169+
170+
expect(activeToggleSwitch).toHaveClass('text-blue-300');
171+
expect(disabledToggleSwitch).toHaveClass('text-blue-200');
172+
});
173+
174+
it('should use `label` classes', () => {
175+
const theme = {
176+
toggleSwitch: {
177+
root: {
178+
label: 'test-label',
179+
},
180+
},
181+
};
182+
render(
183+
<Flowbite theme={{ theme }}>
184+
<ToggleSwitch checked={false} label="Enable" onChange={console.log} type="submit" />
185+
</Flowbite>,
186+
);
187+
188+
expect(label()).toHaveClass('test-label');
189+
});
190+
191+
it('should use `toggle` classes', () => {
192+
const theme = {
193+
toggleSwitch: {
194+
toggle: {
195+
base: 'h-6 w-11',
196+
checked: {
197+
color: {
198+
blue: 'bg-pink-700',
199+
},
200+
},
201+
},
202+
},
203+
};
204+
render(
205+
<Flowbite theme={{ theme }}>
206+
<ToggleSwitch checked label="Enable" onChange={console.log} type="submit" />
207+
</Flowbite>,
208+
);
209+
210+
expect(toggle()).toHaveClass('h-6 w-11 bg-pink-700');
211+
});
212+
});
130213
});
131214

132215
const toggleSwitch = () => screen.getByRole('switch');
216+
217+
const toggleSwitches = () => screen.getAllByRole('switch');
218+
219+
const label = () => screen.getByTestId('flowbite-toggleswitch-label');
220+
221+
const toggle = () => screen.getByTestId('flowbite-toggleswitch-toggle');

src/lib/components/ToggleSwitch/ToggleSwitch.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,23 @@ import { FlowbiteBoolean, FlowbiteColors } from '../Flowbite/FlowbiteTheme';
77
import { useTheme } from '../Flowbite/ThemeContext';
88

99
export interface FlowbiteToggleSwitchTheme {
10+
root: FlowbiteToggleSwitchRootTheme;
11+
toggle: FlowbiteToggleSwitchToggleTheme;
12+
}
13+
14+
export interface FlowbiteToggleSwitchRootTheme {
1015
base: string;
1116
active: FlowbiteBoolean;
12-
toggle: {
13-
base: string;
14-
checked: FlowbiteBoolean & {
15-
color: FlowbiteColors;
16-
};
17-
};
1817
label: string;
1918
}
2019

20+
export interface FlowbiteToggleSwitchToggleTheme {
21+
base: string;
22+
checked: FlowbiteBoolean & {
23+
color: FlowbiteColors;
24+
};
25+
}
26+
2127
export type ToggleSwitchProps = Omit<ComponentProps<'button'>, 'onChange'> & {
2228
checked: boolean;
2329
label: string;
@@ -64,10 +70,11 @@ export const ToggleSwitch: FC<ToggleSwitchProps> = ({
6470
role="switch"
6571
tabIndex={0}
6672
type="button"
67-
className={classNames(theme.base, theme.active[disabled ? 'off' : 'on'], className)}
73+
className={classNames(theme.root.base, theme.root.active[disabled ? 'off' : 'on'], className)}
6874
{...props}
6975
>
7076
<div
77+
data-testid="flowbite-toggleswitch-toggle"
7178
className={classNames(
7279
theme.toggle.base,
7380
theme.toggle.checked[checked ? 'on' : 'off'],
@@ -77,7 +84,7 @@ export const ToggleSwitch: FC<ToggleSwitchProps> = ({
7784
<span
7885
data-testid="flowbite-toggleswitch-label"
7986
id={`${id}-flowbite-toggleswitch-label`}
80-
className={theme.label}
87+
className={theme.root.label}
8188
>
8289
{label}
8390
</span>

src/lib/theme/default.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,9 @@ const theme: FlowbiteTheme = {
381381
},
382382
},
383383
checkbox: {
384-
base: 'h-4 w-4 rounded border border-gray-300 bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600',
384+
root: {
385+
base: 'h-4 w-4 rounded border border-gray-300 bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800 dark:focus:ring-blue-600',
386+
},
385387
},
386388
fileInput: {
387389
base: 'flex',
@@ -419,7 +421,9 @@ const theme: FlowbiteTheme = {
419421
disabled: 'opacity-50',
420422
},
421423
radio: {
422-
base: 'h-4 w-4 border border-gray-300 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:focus:bg-blue-600 dark:focus:ring-blue-600',
424+
root: {
425+
base: 'h-4 w-4 border border-gray-300 focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:focus:bg-blue-600 dark:focus:ring-blue-600',
426+
},
423427
},
424428
rangeSlider: {
425429
base: 'flex',
@@ -545,10 +549,13 @@ const theme: FlowbiteTheme = {
545549
},
546550
},
547551
toggleSwitch: {
548-
base: 'group relative flex items-center rounded-lg focus:outline-none',
549-
active: {
550-
on: 'cursor-pointer',
551-
off: 'cursor-not-allowed opacity-50',
552+
root: {
553+
base: 'group relative flex items-center rounded-lg focus:outline-none',
554+
active: {
555+
on: 'cursor-pointer',
556+
off: 'cursor-not-allowed opacity-50',
557+
},
558+
label: 'ml-3 text-sm font-medium text-gray-900 dark:text-gray-300',
552559
},
553560
toggle: {
554561
base: 'toggle-bg h-6 w-11 rounded-full border group-focus:ring-4 group-focus:ring-blue-500/25',
@@ -576,7 +583,6 @@ const theme: FlowbiteTheme = {
576583
},
577584
},
578585
},
579-
label: 'ml-3 text-sm font-medium text-gray-900 dark:text-gray-300',
580586
},
581587
helperText: {
582588
base: 'mt-2 text-sm',

0 commit comments

Comments
 (0)