diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-30 12:44:11 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:15:27 +0100 |
| commit | 0e52a59917406ad03c174e030c6c1c92ab23449d (patch) | |
| tree | 693bbcc5edbe78ebd2f0050fddbc45c706e0ba61 /src/components/organisms/forms/settings-form | |
| parent | 84a679b0e48ed76eee2fa44d3caac83591aa3c8c (diff) | |
refactor(components): extract SettingsForm component form SettingsModal
We could use an array of items and map over it instead of repeating the
Switch component for each settings but with translations, it becomes
quickly unreadable. So I prefer to keep separate components.
Diffstat (limited to 'src/components/organisms/forms/settings-form')
22 files changed, 645 insertions, 0 deletions
diff --git a/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.module.scss b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.module.scss new file mode 100644 index 0000000..513c95c --- /dev/null +++ b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.module.scss @@ -0,0 +1,10 @@ +@use "../../../../../styles/abstracts/mixins" as mix; + +.tooltip { + @include mix.media("screen") { + @include mix.dimensions("sm") { + inset-inline: 0; + max-width: 100%; + } + } +} diff --git a/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.stories.tsx b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.stories.tsx new file mode 100644 index 0000000..abaf59f --- /dev/null +++ b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.stories.tsx @@ -0,0 +1,21 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { AckeeToggle } from './ackee-toggle'; + +/** + * AckeeToggle - Storybook Meta + */ +export default { + title: 'Organisms/Forms/Settings/Items', + component: AckeeToggle, + argTypes: {}, +} as ComponentMeta<typeof AckeeToggle>; + +const Template: ComponentStory<typeof AckeeToggle> = (args) => ( + <AckeeToggle {...args} /> +); + +/** + * Toggle Stories - Ackee + */ +export const Ackee = Template.bind({}); +Ackee.args = {}; diff --git a/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.test.tsx b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.test.tsx new file mode 100644 index 0000000..30bbf3a --- /dev/null +++ b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.test.tsx @@ -0,0 +1,28 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../../tests/utils'; +import { AckeeProvider } from '../../../../../utils/providers'; +import { AckeeToggle } from './ackee-toggle'; + +describe('AckeeToggle', () => { + it('renders a radio group of two radio buttons', () => { + const defaultValue = 'full'; + + render( + <AckeeProvider + domainId="any-id" + server="https://example.com" + storageKey="molestiae" + tracking={defaultValue} + > + <AckeeToggle /> + </AckeeProvider> + ); + + expect( + rtlScreen.getByRole('radiogroup', { + name: /Tracking:/i, + }) + ).toBeInTheDocument(); + expect(rtlScreen.getAllByRole('radio')).toHaveLength(2); + }); +}); diff --git a/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.tsx b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.tsx new file mode 100644 index 0000000..0eee1fc --- /dev/null +++ b/src/components/organisms/forms/settings-form/ackee-toggle/ackee-toggle.tsx @@ -0,0 +1,116 @@ +import { forwardRef, type ForwardRefRenderFunction } from 'react'; +import { useIntl } from 'react-intl'; +import { useAckee, useBoolean } from '../../../../../utils/hooks'; +import { Legend, List, ListItem } from '../../../../atoms'; +import { + Switch, + type SwitchOption, + type SwitchProps, + Tooltip, + type TooltipProps, +} from '../../../../molecules'; +import styles from './ackee-toggle.module.scss'; + +export type AckeeToggleProps = Omit< + SwitchProps, + 'isInline' | 'items' | 'legend' | 'name' | 'onSwitch' | 'value' +> & + Pick<TooltipProps, 'direction'>; + +const AckeeToggleWithRef: ForwardRefRenderFunction< + HTMLFieldSetElement, + AckeeToggleProps +> = ({ direction, ...props }, ref) => { + const intl = useIntl(); + const [tracking, toggleTracking] = useAckee(); + const { + deactivate: closeTooltip, + state: isTooltipOpened, + toggle: toggleTooltip, + } = useBoolean(false); + + const messages = { + legend: intl.formatMessage({ + defaultMessage: 'Tracking:', + description: 'AckeeToggle: select label', + id: '0gVlI3', + }), + options: { + full: intl.formatMessage({ + defaultMessage: 'Full', + description: 'AckeeToggle: full option name', + id: '5eD6y2', + }), + partial: intl.formatMessage({ + defaultMessage: 'Partial', + description: 'AckeeToggle: partial option name', + id: 'tIZYpD', + }), + }, + tooltip: { + heading: intl.formatMessage({ + defaultMessage: 'Ackee tracking (analytics)', + description: 'AckeeToggle: tooltip title', + id: 'nGss/j', + }), + contents: { + full: intl.formatMessage({ + defaultMessage: + 'Full includes all information from partial as well as information about referrer, operating system, device, browser, screen size and language.', + description: 'AckeeToggle: tooltip message', + id: '7zDlQo', + }), + partial: intl.formatMessage({ + defaultMessage: 'Partial includes only page url, views and duration.', + description: 'AckeeToggle: tooltip message', + id: 'ZB/Aw2', + }), + }, + }, + }; + + const options = [ + { id: 'ackee-full' as const, label: messages.options.full, value: 'full' }, + { + id: 'ackee-partial' as const, + label: messages.options.partial, + value: 'partial', + }, + ] satisfies [SwitchOption, SwitchOption]; + + return ( + <Switch + {...props} + isInline + items={options} + legend={<Legend>{messages.legend}</Legend>} + // eslint-disable-next-line react/jsx-no-literals + name="ackee" + onSwitch={toggleTracking} + ref={ref} + tooltip={ + <Tooltip + className={styles.tooltip} + direction={direction} + heading={messages.tooltip.heading} + isOpen={isTooltipOpened} + onClickOutside={closeTooltip} + onToggle={toggleTooltip} + > + <List> + <ListItem>{messages.tooltip.contents.full}</ListItem> + <ListItem>{messages.tooltip.contents.partial}</ListItem> + </List> + </Tooltip> + } + value={tracking} + /> + ); +}; + +/** + * AckeeToggle component + * + * Render a Toggle component to set tracking. + */ +export const AckeeToggle = forwardRef(AckeeToggleWithRef); diff --git a/src/components/organisms/forms/settings-form/ackee-toggle/index.ts b/src/components/organisms/forms/settings-form/ackee-toggle/index.ts new file mode 100644 index 0000000..7f6313c --- /dev/null +++ b/src/components/organisms/forms/settings-form/ackee-toggle/index.ts @@ -0,0 +1 @@ +export * from './ackee-toggle'; diff --git a/src/components/organisms/forms/settings-form/index.ts b/src/components/organisms/forms/settings-form/index.ts new file mode 100644 index 0000000..7c2a66e --- /dev/null +++ b/src/components/organisms/forms/settings-form/index.ts @@ -0,0 +1 @@ +export * from './settings-form'; diff --git a/src/components/organisms/forms/settings-form/motion-toggle/index.ts b/src/components/organisms/forms/settings-form/motion-toggle/index.ts new file mode 100644 index 0000000..0e35578 --- /dev/null +++ b/src/components/organisms/forms/settings-form/motion-toggle/index.ts @@ -0,0 +1 @@ +export * from './motion-toggle'; diff --git a/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.stories.tsx b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.stories.tsx new file mode 100644 index 0000000..67cea37 --- /dev/null +++ b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.stories.tsx @@ -0,0 +1,21 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { MotionToggle } from './motion-toggle'; + +/** + * MotionToggle - Storybook Meta + */ +export default { + title: 'Organisms/Forms/Settings/Items', + component: MotionToggle, + argTypes: {}, +} as ComponentMeta<typeof MotionToggle>; + +const Template: ComponentStory<typeof MotionToggle> = (args) => ( + <MotionToggle {...args} /> +); + +/** + * Toggle Stories - Motion + */ +export const Motion = Template.bind({}); +Motion.args = {}; diff --git a/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.test.tsx b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.test.tsx new file mode 100644 index 0000000..48c7151 --- /dev/null +++ b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.test.tsx @@ -0,0 +1,21 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../../tests/utils'; +import { MotionProvider } from '../../../../../utils/providers'; +import { MotionToggle } from './motion-toggle'; + +describe('MotionToggle', () => { + it('renders a radio group of two radio buttons', () => { + render( + <MotionProvider attribute="enim" hasReducedMotion storageKey="autem"> + <MotionToggle /> + </MotionProvider> + ); + + expect( + rtlScreen.getByRole('radiogroup', { + name: /Animations:/i, + }) + ).toBeInTheDocument(); + expect(rtlScreen.getAllByRole('radio')).toHaveLength(2); + }); +}); diff --git a/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.tsx b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.tsx new file mode 100644 index 0000000..9ee236a --- /dev/null +++ b/src/components/organisms/forms/settings-form/motion-toggle/motion-toggle.tsx @@ -0,0 +1,76 @@ +import { forwardRef, type ForwardRefRenderFunction } from 'react'; +import { useIntl } from 'react-intl'; +import { useReducedMotion } from '../../../../../utils/hooks'; +import { Legend } from '../../../../atoms'; +import { + Switch, + type SwitchOption, + type SwitchProps, +} from '../../../../molecules'; + +export type MotionToggleProps = Omit< + SwitchProps, + 'isInline' | 'items' | 'legend' | 'name' | 'onSwitch' | 'value' +>; + +const MotionToggleWithRef: ForwardRefRenderFunction< + HTMLFieldSetElement, + MotionToggleProps +> = (props, ref) => { + const intl = useIntl(); + const { isReduced, toggleReducedMotion } = useReducedMotion(); + + const messages = { + legend: intl.formatMessage({ + defaultMessage: 'Animations:', + description: 'MotionToggle: reduce motion label', + id: '/q5csZ', + }), + options: { + on: intl.formatMessage({ + defaultMessage: 'On', + description: 'MotionToggle: activate reduce motion label', + id: 'va65iw', + }), + off: intl.formatMessage({ + defaultMessage: 'Off', + description: 'MotionToggle: deactivate reduce motion label', + id: 'pWKyyR', + }), + }, + }; + + const options: [SwitchOption, SwitchOption] = [ + { + id: 'reduced-motion-on', + label: messages.options.on, + value: 'on', + }, + { + id: 'reduced-motion-off', + label: messages.options.off, + value: 'off', + }, + ]; + + return ( + <Switch + {...props} + isInline + items={options} + legend={<Legend>{messages.legend}</Legend>} + // eslint-disable-next-line react/jsx-no-literals + name="reduced-motion" + onSwitch={toggleReducedMotion} + ref={ref} + value={isReduced ? 'off' : 'on'} + /> + ); +}; + +/** + * MotionToggle component + * + * Render a Toggle component to set reduce motion. + */ +export const MotionToggle = forwardRef(MotionToggleWithRef); diff --git a/src/components/organisms/forms/settings-form/prism-theme-toggle/index.ts b/src/components/organisms/forms/settings-form/prism-theme-toggle/index.ts new file mode 100644 index 0000000..f4e490f --- /dev/null +++ b/src/components/organisms/forms/settings-form/prism-theme-toggle/index.ts @@ -0,0 +1 @@ +export * from './prism-theme-toggle'; diff --git a/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.stories.tsx b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.stories.tsx new file mode 100644 index 0000000..9313bf2 --- /dev/null +++ b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.stories.tsx @@ -0,0 +1,20 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { PrismThemeToggle } from './prism-theme-toggle'; + +/** + * PrismThemeToggle - Storybook Meta + */ +export default { + title: 'Organisms/Forms/Settings/Items', + component: PrismThemeToggle, + argTypes: {}, +} as ComponentMeta<typeof PrismThemeToggle>; + +const Template: ComponentStory<typeof PrismThemeToggle> = (args) => ( + <PrismThemeToggle {...args} /> +); + +/** + * Toggle Stories - Prism theme + */ +export const PrismTheme = Template.bind({}); diff --git a/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.test.tsx b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.test.tsx new file mode 100644 index 0000000..b9f05c4 --- /dev/null +++ b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../../tests/utils'; +import { PrismThemeProvider } from '../../../../../utils/providers'; +import { PrismThemeToggle } from './prism-theme-toggle'; + +describe('PrismThemeToggle', () => { + it('renders a radio group of two radio buttons', () => { + const defaultTheme = 'dark'; + + render( + <PrismThemeProvider + attribute="fuga" + storageKey="sed" + defaultTheme={defaultTheme} + > + <PrismThemeToggle /> + </PrismThemeProvider> + ); + + expect( + rtlScreen.getByRole('radiogroup', { + name: /Code blocks:/i, + }) + ).toBeInTheDocument(); + expect(rtlScreen.getAllByRole('radio')).toHaveLength(2); + }); +}); diff --git a/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.tsx b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.tsx new file mode 100644 index 0000000..b427754 --- /dev/null +++ b/src/components/organisms/forms/settings-form/prism-theme-toggle/prism-theme-toggle.tsx @@ -0,0 +1,78 @@ +import { forwardRef, type ForwardRefRenderFunction } from 'react'; +import { useIntl } from 'react-intl'; +import { usePrismTheme } from '../../../../../utils/hooks'; +import { Icon, Legend } from '../../../../atoms'; +import { + Switch, + type SwitchOption, + type SwitchProps, +} from '../../../../molecules'; + +export type PrismThemeToggleProps = Omit< + SwitchProps, + 'isInline' | 'items' | 'legend' | 'name' | 'onSwitch' | 'value' +>; + +const PrismThemeToggleWithRef: ForwardRefRenderFunction< + HTMLFieldSetElement, + PrismThemeToggleProps +> = (props, ref) => { + const intl = useIntl(); + const { currentTheme, toggleTheme } = usePrismTheme(); + + const messages = { + legend: intl.formatMessage({ + defaultMessage: 'Code blocks:', + description: 'PrismThemeToggle: theme label', + id: 'ftXN+0', + }), + options: { + dark: intl.formatMessage({ + defaultMessage: 'Dark theme', + description: 'PrismThemeToggle: dark theme label', + id: 'og/zWL', + }), + light: intl.formatMessage({ + defaultMessage: 'Light theme', + description: 'PrismThemeToggle: light theme label', + id: 'tsWh8x', + }), + }, + }; + + const options: [SwitchOption, SwitchOption] = [ + { + id: 'code-blocks-light', + // eslint-disable-next-line react/jsx-no-literals + label: <Icon aria-label={messages.options.light} shape="sun" size="sm" />, + value: 'light', + }, + { + id: 'code-blocks-dark', + // eslint-disable-next-line react/jsx-no-literals + label: <Icon aria-label={messages.options.dark} shape="moon" size="sm" />, + value: 'dark', + }, + ]; + + return ( + <Switch + {...props} + isInline + items={options} + legend={<Legend>{messages.legend}</Legend>} + // eslint-disable-next-line react/jsx-no-literals + name="code-blocks" + onSwitch={toggleTheme} + ref={ref} + value={currentTheme} + /> + ); +}; + +/** + * PrismThemeToggle component + * + * Render a Toggle component to set code blocks theme. + */ +export const PrismThemeToggle = forwardRef(PrismThemeToggleWithRef); diff --git a/src/components/organisms/forms/settings-form/settings-form.module.scss b/src/components/organisms/forms/settings-form/settings-form.module.scss new file mode 100644 index 0000000..88a71a7 --- /dev/null +++ b/src/components/organisms/forms/settings-form/settings-form.module.scss @@ -0,0 +1,30 @@ +@use "../../../../styles/abstracts/mixins" as mix; + +.form { + display: flex; + flex-flow: row wrap; + justify-content: space-between; + row-gap: var(--spacing-xs); + column-gap: var(--spacing-lg); + + @include mix.media("screen") { + @include mix.dimensions(null, "2xs", "height") { + row-gap: var(--spacing-2xs); + } + } +} + +.item { + flex-basis: 100%; + + > *:last-child { + flex-basis: 100%; + margin-inline-start: auto; + + @include mix.media("screen") { + @include mix.dimensions("2xs") { + flex-basis: auto; + } + } + } +} diff --git a/src/components/organisms/forms/settings-form/settings-form.stories.tsx b/src/components/organisms/forms/settings-form/settings-form.stories.tsx new file mode 100644 index 0000000..da93bfa --- /dev/null +++ b/src/components/organisms/forms/settings-form/settings-form.stories.tsx @@ -0,0 +1,21 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { SettingsForm } from './settings-form'; + +/** + * SettingsForm - Storybook Meta + */ +export default { + title: 'Organisms/Forms/Settings', + component: SettingsForm, + argTypes: {}, +} as ComponentMeta<typeof SettingsForm>; + +const Template: ComponentStory<typeof SettingsForm> = (args) => ( + <SettingsForm {...args} /> +); + +/** + * Forms Stories - Settings + */ +export const Settings = Template.bind({}); +Settings.args = {}; diff --git a/src/components/organisms/forms/settings-form/settings-form.test.tsx b/src/components/organisms/forms/settings-form/settings-form.test.tsx new file mode 100644 index 0000000..7029595 --- /dev/null +++ b/src/components/organisms/forms/settings-form/settings-form.test.tsx @@ -0,0 +1,15 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../tests/utils'; +import { SettingsForm } from './settings-form'; + +describe('SettingsForm', () => { + it('renders a form with four settings', () => { + const label = 'voluptatem maiores rerum'; + const settingsNumber = 4; + + render(<SettingsForm aria-label={label} />); + + expect(rtlScreen.getByRole('form', { name: label })).toBeInTheDocument(); + expect(rtlScreen.getAllByRole('radiogroup')).toHaveLength(settingsNumber); + }); +}); diff --git a/src/components/organisms/forms/settings-form/settings-form.tsx b/src/components/organisms/forms/settings-form/settings-form.tsx new file mode 100644 index 0000000..117665d --- /dev/null +++ b/src/components/organisms/forms/settings-form/settings-form.tsx @@ -0,0 +1,31 @@ +import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import { Form, type FormProps } from '../../../atoms'; +import { AckeeToggle } from './ackee-toggle'; +import { MotionToggle } from './motion-toggle'; +import { PrismThemeToggle } from './prism-theme-toggle'; +import styles from './settings-form.module.scss'; +import { ThemeToggle } from './theme-toggle'; + +export type SettingsFormProps = Omit<FormProps, 'children'>; + +const SettingsFormWithRef: ForwardRefRenderFunction< + HTMLFormElement, + SettingsFormProps +> = ({ className = '', ...props }, ref) => { + const formClass = `${styles.form} ${className}`; + + return ( + <Form {...props} className={formClass} ref={ref}> + <ThemeToggle className={styles.item} /> + <PrismThemeToggle className={styles.item} /> + <MotionToggle className={styles.item} /> + <AckeeToggle + className={styles.item} + // eslint-disable-next-line react/jsx-no-literals + direction="upwards" + /> + </Form> + ); +}; + +export const SettingsForm = forwardRef(SettingsFormWithRef); diff --git a/src/components/organisms/forms/settings-form/theme-toggle/index.ts b/src/components/organisms/forms/settings-form/theme-toggle/index.ts new file mode 100644 index 0000000..0dbf668 --- /dev/null +++ b/src/components/organisms/forms/settings-form/theme-toggle/index.ts @@ -0,0 +1 @@ +export * from './theme-toggle'; diff --git a/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.stories.tsx b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.stories.tsx new file mode 100644 index 0000000..4742adf --- /dev/null +++ b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.stories.tsx @@ -0,0 +1,20 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { ThemeToggle } from './theme-toggle'; + +/** + * ThemeToggle - Storybook Meta + */ +export default { + title: 'Organisms/Forms/Settings/Items', + component: ThemeToggle, + argTypes: {}, +} as ComponentMeta<typeof ThemeToggle>; + +const Template: ComponentStory<typeof ThemeToggle> = (args) => ( + <ThemeToggle {...args} /> +); + +/** + * Toggle Stories - Theme + */ +export const Theme = Template.bind({}); diff --git a/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.test.tsx b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.test.tsx new file mode 100644 index 0000000..e74842e --- /dev/null +++ b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.test.tsx @@ -0,0 +1,27 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../../../tests/utils'; +import { ThemeProvider } from '../../../../../utils/providers'; +import { ThemeToggle } from './theme-toggle'; + +describe('ThemeToggle', () => { + it('renders a radio group of two radio buttons', () => { + const defaultTheme = 'dark'; + + render( + <ThemeProvider + attribute="voluptas" + storageKey="alias" + defaultTheme={defaultTheme} + > + <ThemeToggle /> + </ThemeProvider> + ); + + expect( + rtlScreen.getByRole('radiogroup', { + name: /Theme:/i, + }) + ).toBeInTheDocument(); + expect(rtlScreen.getAllByRole('radio')).toHaveLength(2); + }); +}); diff --git a/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.tsx b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.tsx new file mode 100644 index 0000000..d719434 --- /dev/null +++ b/src/components/organisms/forms/settings-form/theme-toggle/theme-toggle.tsx @@ -0,0 +1,78 @@ +import { forwardRef, type ForwardRefRenderFunction } from 'react'; +import { useIntl } from 'react-intl'; +import { useTheme } from '../../../../../utils/hooks'; +import { Icon, Legend } from '../../../../atoms'; +import { + Switch, + type SwitchOption, + type SwitchProps, +} from '../../../../molecules'; + +export type ThemeToggleProps = Omit< + SwitchProps, + 'isInline' | 'items' | 'legend' | 'name' | 'onSwitch' | 'value' +>; + +const ThemeToggleWithRef: ForwardRefRenderFunction< + HTMLFieldSetElement, + ThemeToggleProps +> = (props, ref) => { + const intl = useIntl(); + const { resolvedTheme, toggleTheme } = useTheme(); + + const messages = { + legend: intl.formatMessage({ + defaultMessage: 'Theme:', + description: 'ThemeToggle: theme label', + id: 'suXOBu', + }), + options: { + dark: intl.formatMessage({ + defaultMessage: 'Dark theme', + description: 'ThemeToggle: dark theme label', + id: '2QwvtS', + }), + light: intl.formatMessage({ + defaultMessage: 'Light theme', + description: 'ThemeToggle: light theme label', + id: 'Ygea7s', + }), + }, + }; + + const options: [SwitchOption, SwitchOption] = [ + { + id: 'theme-light', + // eslint-disable-next-line react/jsx-no-literals + label: <Icon aria-label={messages.options.light} shape="sun" size="sm" />, + value: 'light', + }, + { + id: 'theme-dark', + // eslint-disable-next-line react/jsx-no-literals + label: <Icon aria-label={messages.options.dark} shape="moon" size="sm" />, + value: 'dark', + }, + ]; + + return ( + <Switch + {...props} + isInline + items={options} + legend={<Legend>{messages.legend}</Legend>} + // eslint-disable-next-line react/jsx-no-literals + name="theme" + onSwitch={toggleTheme} + ref={ref} + value={resolvedTheme} + /> + ); +}; + +/** + * ThemeToggle component + * + * Render a Toggle component to set theme. + */ +export const ThemeToggle = forwardRef(ThemeToggleWithRef); |
