From 05c934adb2abf6af44784069388f13af2966ba38 Mon Sep 17 00:00:00 2001 From: Evgenia Milcheva Date: Thu, 19 Jan 2023 20:16:08 +0200 Subject: [PATCH] feat(theme): add theme support for Checkbox, Radio and ToggleSwitch (#551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ricardo Lüders --- src/lib/components/Checkbox/Checkbox.spec.tsx | 24 ++++- src/lib/components/Checkbox/Checkbox.tsx | 5 +- src/lib/components/Radio/Radio.spec.tsx | 24 ++++- src/lib/components/Radio/Radio.tsx | 5 +- .../ToggleSwitch/ToggleSwitch.spec.tsx | 89 +++++++++++++++++++ .../components/ToggleSwitch/ToggleSwitch.tsx | 23 +++-- src/lib/theme/default.ts | 20 +++-- 7 files changed, 171 insertions(+), 19 deletions(-) diff --git a/src/lib/components/Checkbox/Checkbox.spec.tsx b/src/lib/components/Checkbox/Checkbox.spec.tsx index 9be9e6871..87da064f8 100644 --- a/src/lib/components/Checkbox/Checkbox.spec.tsx +++ b/src/lib/components/Checkbox/Checkbox.spec.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; +import { Flowbite } from '../Flowbite'; import { Checkbox } from './Checkbox'; describe.concurrent('Components / Checkbox', () => { @@ -10,4 +11,25 @@ describe.concurrent('Components / Checkbox', () => { expect(checkbox).toBeInTheDocument(); }); }); + + describe('Theme', () => { + it('should use custom `base` classes', () => { + const theme = { + checkbox: { + root: { + base: 'bg-yellow-400 dark:bg-yellow-40', + }, + }, + }; + render( + + + , + ); + + expect(checkbox()).toHaveClass('bg-yellow-400 dark:bg-yellow-40'); + }); + }); }); + +const checkbox = () => screen.getByRole('checkbox'); diff --git a/src/lib/components/Checkbox/Checkbox.tsx b/src/lib/components/Checkbox/Checkbox.tsx index bf4c93854..4bd75a31f 100644 --- a/src/lib/components/Checkbox/Checkbox.tsx +++ b/src/lib/components/Checkbox/Checkbox.tsx @@ -6,6 +6,9 @@ import { mergeDeep } from '../../helpers/mergeDeep'; import { useTheme } from '../Flowbite/ThemeContext'; export interface FlowbiteCheckboxTheme { + root: FlowbiteCheckboxRootTheme; +} +export interface FlowbiteCheckboxRootTheme { base: string; } @@ -17,7 +20,7 @@ export const Checkbox = forwardRef( ({ theme: customTheme = {}, className, ...props }, ref) => { const theme = mergeDeep(useTheme().theme.checkbox, customTheme); - return ; + return ; }, ); diff --git a/src/lib/components/Radio/Radio.spec.tsx b/src/lib/components/Radio/Radio.spec.tsx index 8b94ae3ae..2fd171c48 100644 --- a/src/lib/components/Radio/Radio.spec.tsx +++ b/src/lib/components/Radio/Radio.spec.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import { describe, expect, it } from 'vitest'; +import { Flowbite } from '../Flowbite'; import { Radio } from './Radio'; describe.concurrent('Components / Radio', () => { @@ -10,4 +11,25 @@ describe.concurrent('Components / Radio', () => { expect(radio).toBeInTheDocument(); }); }); + + describe('Theme', () => { + it('should use custom `base` classes', () => { + const theme = { + radio: { + root: { + base: 'bg-yellow-400 dark:bg-yellow-40', + }, + }, + }; + render( + + + , + ); + + expect(radio()).toHaveClass('bg-yellow-400 dark:bg-yellow-40'); + }); + }); }); + +const radio = () => screen.getByRole('radio'); diff --git a/src/lib/components/Radio/Radio.tsx b/src/lib/components/Radio/Radio.tsx index bce96027f..626092d66 100644 --- a/src/lib/components/Radio/Radio.tsx +++ b/src/lib/components/Radio/Radio.tsx @@ -6,6 +6,9 @@ import { mergeDeep } from '../../helpers/mergeDeep'; import { useTheme } from '../Flowbite/ThemeContext'; export interface FlowbiteRadioTheme { + root: FlowbiteRadioRootTheme; +} +export interface FlowbiteRadioRootTheme { base: string; } @@ -17,7 +20,7 @@ export const Radio = forwardRef( ({ theme: customTheme = {}, className, ...props }, ref) => { const theme = mergeDeep(useTheme().theme.radio, customTheme); - return ; + return ; }, ); diff --git a/src/lib/components/ToggleSwitch/ToggleSwitch.spec.tsx b/src/lib/components/ToggleSwitch/ToggleSwitch.spec.tsx index 35d4d67da..0e4536908 100644 --- a/src/lib/components/ToggleSwitch/ToggleSwitch.spec.tsx +++ b/src/lib/components/ToggleSwitch/ToggleSwitch.spec.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { FC, useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; +import { Flowbite } from '../Flowbite'; import { TextInput } from '../TextInput'; import { ToggleSwitch } from './ToggleSwitch'; @@ -127,6 +128,94 @@ describe('Components / Toggle switch', () => { expect(toggleSwitch()).toHaveAttribute('type', 'submit'); }); }); + + describe('Theme', () => { + it('should use `base` classes', () => { + const theme = { + toggleSwitch: { + root: { + base: 'text-blue-100', + }, + }, + }; + render( + + + , + ); + + expect(toggleSwitch()).toHaveClass('text-blue-100'); + }); + + it('should use `active` classes', () => { + const theme = { + toggleSwitch: { + root: { + active: { + off: 'text-blue-200', + on: 'text-blue-300', + }, + }, + }, + }; + render( + + + + , + ); + const activeToggleSwitch = toggleSwitches()[0]; + const disabledToggleSwitch = toggleSwitches()[1]; + + expect(activeToggleSwitch).toHaveClass('text-blue-300'); + expect(disabledToggleSwitch).toHaveClass('text-blue-200'); + }); + + it('should use `label` classes', () => { + const theme = { + toggleSwitch: { + root: { + label: 'test-label', + }, + }, + }; + render( + + + , + ); + + expect(label()).toHaveClass('test-label'); + }); + + it('should use `toggle` classes', () => { + const theme = { + toggleSwitch: { + toggle: { + base: 'h-6 w-11', + checked: { + color: { + blue: 'bg-pink-700', + }, + }, + }, + }, + }; + render( + + + , + ); + + expect(toggle()).toHaveClass('h-6 w-11 bg-pink-700'); + }); + }); }); const toggleSwitch = () => screen.getByRole('switch'); + +const toggleSwitches = () => screen.getAllByRole('switch'); + +const label = () => screen.getByTestId('flowbite-toggleswitch-label'); + +const toggle = () => screen.getByTestId('flowbite-toggleswitch-toggle'); diff --git a/src/lib/components/ToggleSwitch/ToggleSwitch.tsx b/src/lib/components/ToggleSwitch/ToggleSwitch.tsx index 0c7facec6..dfddef27e 100644 --- a/src/lib/components/ToggleSwitch/ToggleSwitch.tsx +++ b/src/lib/components/ToggleSwitch/ToggleSwitch.tsx @@ -7,17 +7,23 @@ import { FlowbiteBoolean, FlowbiteColors } from '../Flowbite/FlowbiteTheme'; import { useTheme } from '../Flowbite/ThemeContext'; export interface FlowbiteToggleSwitchTheme { + root: FlowbiteToggleSwitchRootTheme; + toggle: FlowbiteToggleSwitchToggleTheme; +} + +export interface FlowbiteToggleSwitchRootTheme { base: string; active: FlowbiteBoolean; - toggle: { - base: string; - checked: FlowbiteBoolean & { - color: FlowbiteColors; - }; - }; label: string; } +export interface FlowbiteToggleSwitchToggleTheme { + base: string; + checked: FlowbiteBoolean & { + color: FlowbiteColors; + }; +} + export type ToggleSwitchProps = Omit, 'onChange'> & { checked: boolean; label: string; @@ -64,10 +70,11 @@ export const ToggleSwitch: FC = ({ role="switch" tabIndex={0} type="button" - className={classNames(theme.base, theme.active[disabled ? 'off' : 'on'], className)} + className={classNames(theme.root.base, theme.root.active[disabled ? 'off' : 'on'], className)} {...props} >
= ({ {label} diff --git a/src/lib/theme/default.ts b/src/lib/theme/default.ts index 3aed82397..9af435377 100644 --- a/src/lib/theme/default.ts +++ b/src/lib/theme/default.ts @@ -381,7 +381,9 @@ const theme: FlowbiteTheme = { }, }, checkbox: { - 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', + root: { + 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', + }, }, fileInput: { base: 'flex', @@ -419,7 +421,9 @@ const theme: FlowbiteTheme = { disabled: 'opacity-50', }, radio: { - 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', + root: { + 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', + }, }, rangeSlider: { base: 'flex', @@ -545,10 +549,13 @@ const theme: FlowbiteTheme = { }, }, toggleSwitch: { - base: 'group relative flex items-center rounded-lg focus:outline-none', - active: { - on: 'cursor-pointer', - off: 'cursor-not-allowed opacity-50', + root: { + base: 'group relative flex items-center rounded-lg focus:outline-none', + active: { + on: 'cursor-pointer', + off: 'cursor-not-allowed opacity-50', + }, + label: 'ml-3 text-sm font-medium text-gray-900 dark:text-gray-300', }, toggle: { 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 = { }, }, }, - label: 'ml-3 text-sm font-medium text-gray-900 dark:text-gray-300', }, helperText: { base: 'mt-2 text-sm',