diff --git a/pro/src/components/IndividualOffer/PriceCategoriesScreen/PriceCategoriesForm.tsx b/pro/src/components/IndividualOffer/PriceCategoriesScreen/PriceCategoriesForm.tsx index 3eb7df66e02..ecd5bd18e81 100644 --- a/pro/src/components/IndividualOffer/PriceCategoriesScreen/PriceCategoriesForm.tsx +++ b/pro/src/components/IndividualOffer/PriceCategoriesScreen/PriceCategoriesForm.tsx @@ -15,6 +15,7 @@ import { Button } from 'ui-kit/Button/Button' import { ButtonVariant, IconPositionEnum } from 'ui-kit/Button/types' import { Checkbox } from 'ui-kit/form/Checkbox/Checkbox' import { PriceInput } from 'ui-kit/form/PriceInput/PriceInput' +import { CheckboxVariant } from 'ui-kit/form/shared/BaseCheckbox/BaseCheckbox' import { TextInput } from 'ui-kit/form/TextInput/TextInput' import { InfoBox } from 'ui-kit/InfoBox/InfoBox' @@ -232,7 +233,7 @@ export const PriceCategoriesForm = ({ label="Accepter les réservations “Duo“" name="isDuo" disabled={isDisabled} - withBorder + variant={CheckboxVariant.BOX} /> diff --git a/pro/src/components/IndividualOffer/StocksThing/StocksThing.tsx b/pro/src/components/IndividualOffer/StocksThing/StocksThing.tsx index 5023d4d7830..61e70645f36 100644 --- a/pro/src/components/IndividualOffer/StocksThing/StocksThing.tsx +++ b/pro/src/components/IndividualOffer/StocksThing/StocksThing.tsx @@ -34,6 +34,7 @@ import { Quantity, QuantityInput, } from 'ui-kit/form/QuantityInput/QuantityInput' +import { CheckboxVariant } from 'ui-kit/form/shared/BaseCheckbox/BaseCheckbox' import { TextInput } from 'ui-kit/form/TextInput/TextInput' import { InfoBox } from 'ui-kit/InfoBox/InfoBox' @@ -469,7 +470,7 @@ export const StocksThing = ({ offer }: StocksThingProps): JSX.Element => { label="Accepter les réservations “Duo“" name="isDuo" disabled={isDisabled} - withBorder + variant={CheckboxVariant.BOX} /> diff --git a/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.module.scss b/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.module.scss index 91fa1808199..1603dd4fd27 100644 --- a/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.module.scss +++ b/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.module.scss @@ -1,11 +1,5 @@ @use "styles/mixins/_rem.scss" as rem; -.accessibility-checkbox-group { - legend { - margin-bottom: rem.torem(24px); - } -} - .info-banners { margin-bottom: rem.torem(24px); } diff --git a/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.tsx b/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.tsx index d1ffe5f7a6c..a273de6fdf0 100644 --- a/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.tsx +++ b/pro/src/pages/IndividualOffer/IndividualOfferInformations/components/UsefulInformationForm/UsefulInformationForm.tsx @@ -253,7 +253,6 @@ export const UsefulInformationForm = ({ } export const Checkbox = ({ name, className, hideFooter, - refForInput, ...props }: CheckboxProps): JSX.Element => { const [field, meta] = useField({ name, type: 'checkbox' }) const hasError = meta.touched && !!meta.error return (
- + {!hideFooter && (
{hasError && {meta.error}} diff --git a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.module.scss b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.module.scss index 1d10271d070..54b8a4aba89 100644 --- a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.module.scss +++ b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.module.scss @@ -1,13 +1,12 @@ @use "styles/mixins/_rem.scss" as rem; .checkbox-group { - &-item { - // RomainC: I don't understand why it doesn't exactly match the sketch - margin-bottom: rem.torem(12px); - width: max-content; + display: flex; + flex-direction: column; + gap: rem.torem(8px); +} - &:last-of-type { - margin-bottom: 0; - } - } +.inline { + flex-flow: row wrap; + gap: rem.torem(16px); } diff --git a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.stories.tsx b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.stories.tsx index 12448ce75b3..169aeeed449 100644 --- a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.stories.tsx +++ b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.stories.tsx @@ -1,5 +1,7 @@ import { Formik } from 'formik' +import { CheckboxVariant } from '../shared/BaseCheckbox/BaseCheckbox' + import { CheckboxGroup } from './CheckboxGroup' export default { @@ -7,7 +9,22 @@ export default { component: CheckboxGroup, decorators: [ (Story: any) => ( - {}}> + {}} + > {({ getFieldProps }) => { return }} @@ -24,5 +41,105 @@ export const Default = { })), groupName: 'checkBoxes', legend: 'This is the legend', + disabled: false, + }, +} + +export const Box = { + args: { + ...Default.args, + variant: CheckboxVariant.BOX, + }, +} + +export const BoxInline = { + args: { + ...Default.args, + inline: true, + variant: CheckboxVariant.BOX, + }, +} + +export const BoxWithChildren = { + args: { + ...Default.args, + group: ['foo', 'bar', 'baz'].map((item) => ({ + label: item, + name: `checkBoxes.${item}`, + childrenOnChecked: Child content for {item}, + })), + variant: CheckboxVariant.BOX, + }, +} + +export const BoxWithCheckboxGroupChildren = { + args: { + ...Default.args, + group: ['foo', 'bar', 'baz'].map((item, i) => ({ + label: item, + name: `checkBoxes.${item}`, + childrenOnChecked: ( + + ), + })), + variant: CheckboxVariant.BOX, + }, +} + +export const BoxWithCheckboxGroupChildrenNoLegend = { + args: { + ...Default.args, + group: ['foo', 'bar', 'baz'].map((item, i) => ({ + label: {item}, + name: `checkBoxes.${item}`, + childrenOnChecked: ( + + ), + })), + variant: CheckboxVariant.BOX, + }, +} + +export const BoxWithCheckboxGroupChildrenDisabled = { + args: { + ...Default.args, + disabled: true, + group: ['foo', 'bar', 'baz'].map((item, i) => ({ + label: {item}, + name: `checkBoxes.${item}`, + childrenOnChecked: ( + + ), + })), + variant: CheckboxVariant.BOX, + groupName: 'checkBoxes', + legend: 'This is the legend', }, } diff --git a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.tsx b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.tsx index bd59ecf5a81..d00bb676f02 100644 --- a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.tsx +++ b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroup.tsx @@ -1,61 +1,79 @@ -import cn from 'classnames' +import classNames from 'classnames' import { useField } from 'formik' +import { RequireAtLeastOne } from '../RadioGroup/RadioGroup' +import { CheckboxVariant } from '../shared/BaseCheckbox/BaseCheckbox' import { FieldSetLayout } from '../shared/FieldSetLayout/FieldSetLayout' import styles from './CheckboxGroup.module.scss' import { CheckboxGroupItem } from './CheckboxGroupItem' -interface CheckboxGroupProps { - groupName: string - legend: string - group: { - name: string - label: string - description?: string - icon?: string - onChange?: (event: React.ChangeEvent) => void - }[] - className?: string - disabled?: boolean - isOptional?: boolean -} +type CheckboxGroupProps = RequireAtLeastOne< + { + groupName: string + legend?: string + describedBy?: string + group: { + name: string + label: string + icon?: string + onChange?: (event: React.ChangeEvent) => void + childrenOnChecked?: JSX.Element + }[] + disabled?: boolean + isOptional?: boolean + variant?: CheckboxVariant + inline?: boolean + }, + 'legend' | 'describedBy' +> export const CheckboxGroup = ({ group, groupName, legend, - className, + describedBy, disabled, isOptional, + variant, + inline = false, }: CheckboxGroupProps): JSX.Element => { const [, meta, helpers] = useField({ name: groupName }) const hasError = meta.touched && !!meta.error return ( - {group.map((item) => ( -
- - !meta.touched ? helpers.setTouched(true) : null - } - disabled={disabled} - onChange={item.onChange} - {...(hasError ? { ariaDescribedBy: `error-${groupName}` } : {})} - /> -
- ))} +
+ {group.map((item) => ( +
+ + !meta.touched ? helpers.setTouched(true) : null + } + disabled={disabled} + onChange={item.onChange} + {...(hasError ? { ariaDescribedBy: `error-${groupName}` } : {})} + variant={variant} + childrenOnChecked={item.childrenOnChecked} + /> +
+ ))} +
) } diff --git a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroupItem.tsx b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroupItem.tsx index 0aa7734c9e8..45aec419418 100644 --- a/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroupItem.tsx +++ b/pro/src/ui-kit/form/CheckboxGroup/CheckboxGroupItem.tsx @@ -1,6 +1,9 @@ import { useField } from 'formik' -import { BaseCheckbox } from 'ui-kit/form/shared/BaseCheckbox/BaseCheckbox' +import { + BaseCheckbox, + CheckboxVariant, +} from 'ui-kit/form/shared/BaseCheckbox/BaseCheckbox' interface CheckboxGroupItemProps { setGroupTouched(): void @@ -12,6 +15,8 @@ interface CheckboxGroupItemProps { disabled?: boolean onChange?: (event: React.ChangeEvent) => void ariaDescribedBy?: string + variant?: CheckboxVariant + childrenOnChecked?: JSX.Element } export const CheckboxGroupItem = ({ @@ -23,6 +28,8 @@ export const CheckboxGroupItem = ({ disabled, onChange, ariaDescribedBy, + variant, + childrenOnChecked, }: CheckboxGroupItemProps): JSX.Element => { const [field] = useField({ name, type: 'checkbox' }) @@ -43,6 +50,8 @@ export const CheckboxGroupItem = ({ onChange={onCustomChange} disabled={disabled} ariaDescribedBy={ariaDescribedBy} + variant={variant} + childrenOnChecked={childrenOnChecked} /> ) } diff --git a/pro/src/ui-kit/form/RadioGroup/RadioGroup.stories.tsx b/pro/src/ui-kit/form/RadioGroup/RadioGroup.stories.tsx index 7464a58e386..feb3cce7818 100644 --- a/pro/src/ui-kit/form/RadioGroup/RadioGroup.stories.tsx +++ b/pro/src/ui-kit/form/RadioGroup/RadioGroup.stories.tsx @@ -111,3 +111,27 @@ export const InnerRadioGroupWithNoLegend = { ], }, } + +export const WithChildrenDisabled = { + args: { + ...defaultArgs, + disabled: true, + variant: RadioVariant.BOX, + group: defaultArgs.group.map((g, i) => ({ + ...g, + childrenOnChecked: ( + + ), + })), + name: 'name 3', + }, +} diff --git a/pro/src/ui-kit/form/RadioGroup/RadioGroup.tsx b/pro/src/ui-kit/form/RadioGroup/RadioGroup.tsx index a845219da6e..82130c054d1 100644 --- a/pro/src/ui-kit/form/RadioGroup/RadioGroup.tsx +++ b/pro/src/ui-kit/form/RadioGroup/RadioGroup.tsx @@ -7,7 +7,7 @@ import { FieldSetLayout } from '../shared/FieldSetLayout/FieldSetLayout' import styles from './RadioGroup.module.scss' -type RequireAtLeastOne = Pick< +export type RequireAtLeastOne = Pick< T, Exclude > & diff --git a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.module.scss b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.module.scss index 1ff51b640f3..8b0a82f77cd 100644 --- a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.module.scss +++ b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.module.scss @@ -5,10 +5,6 @@ @use "styles/mixins/_a11y.scss" as a11y; .base-checkbox { - display: inline-flex; - cursor: pointer; - width: 100%; - &-label-row { display: inline-flex; width: 100%; @@ -16,10 +12,8 @@ } &-label { - display: inline-flex; - flex-direction: column; width: 100%; - justify-items: center; + cursor: pointer; } &-description { @@ -32,8 +26,8 @@ &-icon { margin-right: rem.torem(8px); display: inline-flex; + vertical-align: text-bottom; flex-direction: column; - vertical-align: middle; &-svg { width: rem.torem(20px); @@ -43,7 +37,6 @@ &-input { appearance: none; - background-color: var(--color-input-bg-color); border: rem.torem(2px) solid var(--color-grey-dark); border-radius: rem.torem(3px); transition: @@ -82,23 +75,8 @@ } &:focus-visible { - outline: rem.torem(1px) solid var(--color-input-text-color); + outline: rem.torem(1px) solid var(--color-black); outline-offset: rem.torem(4px); - border-radius: rem.torem(4px); - } - } - - &:hover { - text-decoration: underline; - - .base-checkbox-input { - border-color: var(--color-secondary-light); - - &:checked, - &:indeterminate { - border-color: var(--color-grey-dark); - background-color: var(--color-grey-dark); - } } } @@ -141,25 +119,87 @@ } } -.with-border { +.base-checkbox-row { + display: inline-flex; + width: 100%; + + &:hover { + text-decoration: underline; + + .base-checkbox-input { + border-color: var(--color-secondary-light); + + &:checked, + &:indeterminate { + border-color: var(--color-grey-dark); + background-color: var(--color-grey-dark); + } + } + } +} + +.box-variant, +.inline-box-variant { border: rem.torem(1px) solid var(--color-grey-semi-dark); - border-radius: rem.torem(6px); + border-radius: rem.torem(8px); min-width: 100%; padding-left: rem.torem(16px); - padding-top: rem.torem(16px); - padding-bottom: rem.torem(16px); + + &.is-checked { + background-color: var(--color-background-secondary); + border-color: var(--color-secondary); + } + + &.is-disabled .base-checkbox-input:checked { + background-color: var(--color-grey-semi-dark); + border-color: var(--color-grey-semi-dark); + } + + &.has-error { + background-color: transparent; + border-color: var(--color-error); + } + + &.is-disabled { + background-color: var(--color-grey-light); + border-color: var(--color-grey-medium); + } .base-checkbox-input:focus-visible { outline: none; } + .base-checkbox-label { + padding: rem.torem(16px) rem.torem(16px) rem.torem(16px) 0; + } + &:focus-within { - outline: rem.torem(1px) solid var(--color-input-text-color); + outline: rem.torem(1px) solid var(--color-black); outline-offset: rem.torem(4px); - border-radius: rem.torem(4px); + } + + @supports selector(:has(*)) { + &:focus-within { + outline: none; + } + + &:has(.base-checkbox-input:focus-visible) { + outline: rem.torem(1px) solid var(--color-black); + outline-offset: rem.torem(4px); + } } } .visually-hidden { @include a11y.visually-hidden; } + +.base-checkbox-children-on-checked { + padding: 0 rem.torem(16px) rem.torem(16px) 0; +} + +.base-checkbox.has-children { + &.is-checked:not(.is-disabled) { + background: none; + } +} diff --git a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.spec.tsx b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.spec.tsx new file mode 100644 index 00000000000..6a605a216d2 --- /dev/null +++ b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.spec.tsx @@ -0,0 +1,36 @@ +import { render, screen } from '@testing-library/react' + +import { BaseCheckbox } from './BaseCheckbox' + +describe('BaseCHeckbox', () => { + it('should render a checkbox with a label', () => { + render( {}} />) + + expect(screen.getByLabelText('My label')).toBeChecked() + }) + + it('should show the checkbox children when the input is checked', () => { + render( + {}} + childrenOnChecked={My child} + /> + ) + + expect(screen.getByText('My child')).toBeInTheDocument() + }) + + it('should not show the checkbox children when the input is not checked', () => { + render( + {}} + childrenOnChecked={My child} + /> + ) + + expect(screen.queryByText('My child')).not.toBeInTheDocument() + }) +}) diff --git a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.stories.tsx b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.stories.tsx index 9d0cd4829d1..f635bb7d1e0 100644 --- a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.stories.tsx +++ b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.stories.tsx @@ -2,7 +2,7 @@ import type { StoryObj } from '@storybook/react' import strokeAccessibilityEye from 'icons/stroke-accessibility-eye.svg' -import { BaseCheckbox } from './BaseCheckbox' +import { BaseCheckbox, CheckboxVariant } from './BaseCheckbox' export default { title: 'ui-kit/forms/shared/BaseCheckbox', @@ -11,23 +11,41 @@ export default { export const Default: StoryObj = { args: { - label: 'Checkbox Label', + label: 'Checkbox label', icon: strokeAccessibilityEye, }, } -export const WithPartialCheck: StoryObj = { +export const Checked: StoryObj = { args: { - label: 'Checkbox Label', - icon: strokeAccessibilityEye, + label: 'Checkbox checked', + checked: true, + onChange: () => {}, + }, +} + +export const PartialCheck: StoryObj = { + args: { + label: 'Checkbox with partial check', partialCheck: true, }, } -export const WithBorder: StoryObj = { +export const Box: StoryObj = { args: { - label: 'Checkbox Label with border', - icon: strokeAccessibilityEye, - withBorder: true, + label: 'Checkbox with border', + variant: CheckboxVariant.BOX, + checked: true, + onChange: () => {}, + }, +} + +export const BoxWithChildren: StoryObj = { + args: { + label: 'Checkbox with border and children', + variant: CheckboxVariant.BOX, + checked: true, + childrenOnChecked: Child content, + onChange: () => {}, }, } diff --git a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.tsx b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.tsx index fbcc1d99fd8..677bd0e8447 100644 --- a/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.tsx +++ b/pro/src/ui-kit/form/shared/BaseCheckbox/BaseCheckbox.tsx @@ -11,6 +11,11 @@ export enum PartialCheck { UNCHECKED = 'unchecked', } +export enum CheckboxVariant { + DEFAULT = 'default', + BOX = 'box', +} + export interface BaseCheckboxProps extends React.InputHTMLAttributes { label: string | React.ReactNode @@ -19,11 +24,12 @@ export interface BaseCheckboxProps inputClassName?: string labelClassName?: string icon?: string - withBorder?: boolean + variant?: CheckboxVariant partialCheck?: boolean exceptionnallyHideLabelDespiteA11y?: boolean description?: string ariaDescribedBy?: string + childrenOnChecked?: JSX.Element } export const BaseCheckbox = forwardRef( @@ -35,11 +41,12 @@ export const BaseCheckbox = forwardRef( inputClassName, labelClassName, icon, - withBorder, + variant = CheckboxVariant.DEFAULT, partialCheck, exceptionnallyHideLabelDespiteA11y, description, ariaDescribedBy, + childrenOnChecked, ...props }: BaseCheckboxProps, forwardedRef: ForwardedRef @@ -61,41 +68,52 @@ export const BaseCheckbox = forwardRef( labelClassName ) const containerClasses = cn(styles['base-checkbox'], className, { - [styles['with-border']]: withBorder, + [styles['box-variant']]: variant === CheckboxVariant.BOX, [styles['has-error']]: hasError, [styles['is-disabled']]: props.disabled, + [styles['is-checked']]: props.checked, + [styles['has-children']]: childrenOnChecked, }) return (
- - - {icon && ( - - - +
+ + + + +
+
+ {childrenOnChecked && props.checked && ( +
+ {childrenOnChecked} +
)} - - +
) } diff --git a/pro/src/ui-kit/form/shared/BaseRadio/BaseRadio.tsx b/pro/src/ui-kit/form/shared/BaseRadio/BaseRadio.tsx index 631ed83c8b9..5a2c7180d56 100644 --- a/pro/src/ui-kit/form/shared/BaseRadio/BaseRadio.tsx +++ b/pro/src/ui-kit/form/shared/BaseRadio/BaseRadio.tsx @@ -28,7 +28,6 @@ export const BaseRadio = ({ ...props }: BaseRadioProps): JSX.Element => { const id = useId() - const childrenContainerId = useId() return (
-
- {childrenOnChecked && props.checked && ( -
- {childrenOnChecked} -
- )} -
+ {childrenOnChecked && props.checked && ( +
+ {childrenOnChecked} +
+ )}
) }