diff --git a/client/look-and-feel/css/src/Form/Checkbox/Checkbox.scss b/client/look-and-feel/css/src/Form/Checkbox/Checkbox.scss index 15066c086..03c929d3d 100644 --- a/client/look-and-feel/css/src/Form/Checkbox/Checkbox.scss +++ b/client/look-and-feel/css/src/Form/Checkbox/Checkbox.scss @@ -133,6 +133,10 @@ } } + &-select.subgrid label { + width: inherit; + } + & label input[type="checkbox"] { position: absolute; margin-right: 0.5rem; diff --git a/client/look-and-feel/css/src/Form/Checkbox/Checkbox.stories.ts b/client/look-and-feel/css/src/Form/Checkbox/Checkbox.stories.ts index 77d57a7fa..8bc77aa78 100644 --- a/client/look-and-feel/css/src/Form/Checkbox/Checkbox.stories.ts +++ b/client/look-and-feel/css/src/Form/Checkbox/Checkbox.stories.ts @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/html"; import "./Checkbox.scss"; +import { render, renderBasic } from "./render"; const meta: Meta = { title: "Components/Form/Input/Checkbox", @@ -7,448 +8,106 @@ const meta: Meta = { export default meta; -export const Basic: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- -
-
- -
-
- -
-
- -
`; - - return container; - }, - args: {}, - argTypes: {}, +export type TTitle = { + title: string; + subtitle?: string; + description?: string; }; -export const BasicWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- -
-
- - Veuillez cocher une case -
`; - - return container; - }, - args: {}, - argTypes: {}, +export type TCheckboxValue = { + label?: string; + name: string; + group?: TTitle; + withIcon?: boolean; + checked?: boolean; + disabled?: boolean; }; -export const Vertical: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- - - -
`; - - return container; - }, - args: {}, - argTypes: {}, -}; - -export const VerticalWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- - -
-
- - Veuillez sélectionner au moins une ville -
`; - - return container; - }, - args: {}, - argTypes: {}, -}; - -export const VerticalWithLabel: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.className = "af-checkbox__container"; - const span = document.createElement("span"); - span.className = "af-checkbox__label"; - span.innerHTML = "Quelle ville ?"; - const required = document.createElement("span"); - required.ariaHidden = "true"; - required.innerHTML = " *"; - span.appendChild(required); - container.appendChild(span); - const group = document.createElement("div"); - group.role = "group"; - group.className = `af-checkbox af-checkbox-select af-checkbox-select--vertical`; - const label1 = document.createElement("label"); - label1.htmlFor = "id-label1"; - const input1 = document.createElement("input"); - input1.type = "checkbox"; - input1.id = "id-label1"; - label1.appendChild(input1); - const checkboxIcons1 = document.createElement("div"); - checkboxIcons1.className = "af-checkbox__icons"; - checkboxIcons1.innerHTML = ` - `; - label1.appendChild(checkboxIcons1); - const content1 = document.createElement("div"); - content1.className = "af-checkbox__content"; - const contentDescription1 = document.createElement("div"); - contentDescription1.className = "af-checkbox__content-description"; - const labelDescription1 = document.createElement("span"); - labelDescription1.innerHTML = "Bruxelles"; - const description1 = document.createElement("span"); - description1.innerHTML = "Capitale de la Belgique"; - contentDescription1.appendChild(labelDescription1); - contentDescription1.appendChild(description1); - content1.innerHTML = ``; - content1.appendChild(contentDescription1); - label1.appendChild(content1); - - const label2 = document.createElement("label"); - label2.htmlFor = "id-label2"; - const input2 = document.createElement("input"); - input2.type = "checkbox"; - input2.id = "id-label2"; - label2.appendChild(input2); - const checkboxIcons2 = document.createElement("div"); - checkboxIcons2.className = "af-checkbox__icons"; - checkboxIcons2.innerHTML = ` - `; - label2.appendChild(checkboxIcons2); - const content2 = document.createElement("div"); - content2.className = "af-checkbox__content"; - const contentDescription2 = document.createElement("div"); - contentDescription2.className = "af-checkbox__content-description"; - const labelDescription2 = document.createElement("span"); - labelDescription2.innerHTML = "Paris"; - const description2 = document.createElement("span"); - description2.innerHTML = "Capitale de la France"; - const subtitle2 = document.createElement("span"); - subtitle2.innerHTML = "Nord"; - contentDescription2.appendChild(labelDescription2); - contentDescription2.appendChild(description2); - contentDescription2.appendChild(subtitle2); - content2.innerHTML = ``; - content2.appendChild(contentDescription2); - label2.appendChild(content2); - group.appendChild(label1); - group.appendChild(label2); - container.appendChild(group); - return container; +export const Basic: StoryObj = { + render: renderBasic, + args: { + name: "checkbox1", + label: + "J'accepte la convention de preuve relative à ma demande de versement complémentaire sur internet et déclare en accepter expressément les conditions. Je déclare être pleinement informé(e) qu'AXA, en sa qualité d'organisme financier, est soumise aux obligations légales issues principalement du code monétaire et financier en matière de lutte contre le blanchiment des capitaux et le financement du terrorisme.", + disabled: false, + checked: false, + error: "", }, - args: {}, argTypes: {}, }; -export const Horizontal: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- - - -
`; - - return container; +export const Select: StoryObj = { + render, + args: { + label: "Quelle ville ?", + isRequired: true, + error: "", + vertical: true, + disableParis: false, + disableBruxelles: false, + disableLille: true, + checkParis: false, + checkBruxelles: true, + checkLille: true, + showIconParis: true, + showIconBruxelles: true, + showIconLille: true, + checkboxList: [ + { + name: "Paris", + labelGroup: { + title: "Paris", + subtitle: "Capitale de la France", + description: "Nord", + }, + }, + { + name: "Bruxelles", + labelGroup: { title: "Bruxelles", subtitle: "Capitale de la Belgique" }, + }, + { + name: "Lille", + labelGroup: { title: "Lille" }, + }, + ], }, - args: {}, - argTypes: {}, -}; - -export const HorizontalWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- - -
-
- - Veuillez sélectionner au moins une ville -
`; - - return container; + argTypes: { + checkboxList: { control: false }, }, - args: {}, - argTypes: {}, + decorators: [ + (story, { args }) => { + const { + disableParis, + disableBruxelles, + disableLille, + checkParis, + checkBruxelles, + checkLille, + showIconParis, + showIconBruxelles, + showIconLille, + checkboxList, + ...restArgs + } = args; + checkboxList[0] = { + ...checkboxList[0], + disabled: disableParis, + checked: checkParis, + withIcon: showIconParis, + }; + checkboxList[1] = { + ...checkboxList[1], + disabled: disableBruxelles, + checked: checkBruxelles, + withIcon: showIconBruxelles, + }; + checkboxList[2] = { + ...checkboxList[2], + disabled: disableLille, + checked: checkLille, + withIcon: showIconLille, + }; + return story({ args: { checkboxList, ...restArgs } }); + }, + ], }; diff --git a/client/look-and-feel/css/src/Form/Checkbox/render.ts b/client/look-and-feel/css/src/Form/Checkbox/render.ts new file mode 100644 index 000000000..f180d4729 --- /dev/null +++ b/client/look-and-feel/css/src/Form/Checkbox/render.ts @@ -0,0 +1,156 @@ +import type { Args } from "@storybook/html"; +import { + createNode, + div, + input, + label, + span, + svg, + type Tattributes, + Tchildren, +} from "../../utils"; +import { TCheckboxValue } from "./Checkbox.stories"; + +export const getInput = (args: Args) => { + return input({ + id: args.id, + name: args.name, + "aria-invalid": `${Boolean(args.error)}`, + type: "checkbox", + value: args.value, + disabled: args.disabled, + checked: args.checked, + } as Tattributes); +}; + +export const getIconDiv = () => { + const svgUnchecked = svg({ + class: "af-checkbox__unchecked", + path: "M19 5v14H5V5zm0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2", + }); + const svgChecked = svg({ + class: "af-checkbox__checked", + path: "M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2m-9 14-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8z", + }); + + const iconContainer = div([], { + class: "af-checkbox__icons", + }); + + iconContainer.innerHTML = `${svgUnchecked.outerHTML}${svgChecked.outerHTML}`; + return iconContainer; +}; + +export const getSpan = (spanLabel: string) => { + return createNode({ node: "span", children: [spanLabel] }); +}; + +export const getDescriptionDiv = (children: Tchildren) => { + return div(children, { + class: "af-checkbox__content-description", + }); +}; + +export const renderLabelBasic = (args: Args) => { + const inputCheckbox = getInput({ + id: `id-${args.name}`, + name: args.name, + error: !!args.error, + disabled: args.disabled && !args.error, + checked: args.checked && !args.error, + }); + const iconDiv = getIconDiv(); + return label([inputCheckbox, iconDiv, args.label], { + htmlFor: `id-${args.name}`, + }); +}; + +export const renderLabel = (args: Args, isError: boolean) => { + const inputCheckbox = getInput({ + id: `id-${args.name}`, + name: args.name, + error: isError, + disabled: args.disabled && !args.error, + checked: args.checked && !args.error, + }); + const iconDiv = getIconDiv(); + const description: (string | Node)[] = []; + if (args.labelGroup.title) { + description.push(getSpan(args.labelGroup.title)); + } + if (args.labelGroup.subtitle) { + description.push(getSpan(args.labelGroup.subtitle)); + } + if (args.labelGroup.description) { + description.push(getSpan(args.labelGroup.description)); + } + const descriptionGroup = getDescriptionDiv(description); + + const group = div([], { + class: "af-checkbox__content", + }); + if (args.withIcon) { + const icon = svg({ + class: "af-checkbox__icon", + path: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z", + }); + group.innerHTML = `${icon.outerHTML}`; + } + group.innerHTML += `${(descriptionGroup as HTMLElement).outerHTML}`; + + return label([inputCheckbox, iconDiv, group]); +}; + +export const renderError = (error: string) => { + const errorDiv = div([], { + class: "af-checkbox__error", + ariaLive: "assertive", + }); + const icon = svg({ + path: "M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2M12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8", + }); + errorDiv.innerHTML = `${icon.outerHTML}${error}`; + return errorDiv; +}; +export const renderBasic = (args: Args) => { + const children = [div([renderLabelBasic(args)], { class: "af-checkbox" })]; + if (args.error) { + children.push(renderError(args.error)); + } + return div(children); +}; + +export const render = (args: Args) => { + const positionClass = args.vertical + ? "af-checkbox-select--vertical" + : "af-checkbox-select--horizontal"; + const attributes: Tattributes = { + role: "group", + class: `af-checkbox af-checkbox-select ${positionClass} ${args.class}`, + }; + const checkboxLabels: (string | Node)[] = []; + args.checkboxList.forEach((checkboxLabel: TCheckboxValue) => { + checkboxLabels.push(renderLabel(checkboxLabel, !!args.error)); + }); + + const container = div(checkboxLabels, attributes); + const childrenContainer = []; + if (args.label) { + const spanLabelChildren: (string | Node)[] = [args.label]; + if (args.isRequired) { + spanLabelChildren.push(span([" *"], { ariaHidden: "true" })); + } + childrenContainer.push( + span(spanLabelChildren, { class: "af-checkbox__label" }), + ); + } + childrenContainer.push(container); + + if (args.error) { + childrenContainer.push(renderError(args.error)); + } + + return div(childrenContainer, { + class: `af-checkbox__container ${args.class}`, + }); +}; diff --git a/client/look-and-feel/css/src/Form/Radio/Radio.scss b/client/look-and-feel/css/src/Form/Radio/Radio.scss index ccc46f974..6fbb43b25 100644 --- a/client/look-and-feel/css/src/Form/Radio/Radio.scss +++ b/client/look-and-feel/css/src/Form/Radio/Radio.scss @@ -128,6 +128,10 @@ } } + &-select.subgrid label { + width: inherit; + } + &-select[aria-invalid="true"] label { --box-shadow-color: var(--color-red-700); diff --git a/client/look-and-feel/css/src/Form/Radio/Radio.stories.ts b/client/look-and-feel/css/src/Form/Radio/Radio.stories.ts index e49a9015b..b987f8a89 100644 --- a/client/look-and-feel/css/src/Form/Radio/Radio.stories.ts +++ b/client/look-and-feel/css/src/Form/Radio/Radio.stories.ts @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/html"; import "./Radio.scss"; +import { render, renderBasic } from "./render"; const meta: Meta = { title: "Components/Form/Input/Radio", @@ -7,521 +8,105 @@ const meta: Meta = { export default meta; -export const Basic: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = `
- -
-
- -
-
- -
-
- -
`; - - return container; - }, - args: {}, - argTypes: {}, -}; - -const InputError = ` -
- - Veuillez sélectionner au moins une ville -
`; - -export const BasicWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.innerHTML = ` -
- -
-
- - - Veuillez cocher une case - -
`; - - return container; - }, - args: {}, - argTypes: {}, -}; - -export const Vertical: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.classList.add("af-radio__container"); - container.innerHTML = `
- - - -
`; - - return container; - }, - args: {}, - argTypes: {}, +export type TTitle = { + title: string; + subtitle?: string; + description?: string; }; -export const VerticalWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.classList.add("af-radio__container"); - container.innerHTML = `
- - -
- ${InputError}`; - - return container; - }, - args: {}, - argTypes: {}, +export type TRadioValue = { + label?: string; + name: string; + group?: TTitle; + withIcon?: boolean; + checked?: boolean; + disabled?: boolean; }; -export const VerticalWithLabel: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.classList.add("af-radio__container"); - container.innerHTML = ` - - Quelle ville ? - -
- - - - - -
- `; - - return container; +export const Basic: StoryObj = { + render: renderBasic, + args: { + name: "radio1", + label: + "J'accepte la convention de preuve relative à ma demande de versement complémentaire sur internet et déclare en accepter expressément les conditions. Je déclare être pleinement informé(e) qu'AXA, en sa qualité d'organisme financier, est soumise aux obligations légales issues principalement du code monétaire et financier en matière de lutte contre le blanchiment des capitaux et le financement du terrorisme.", + disabled: false, + checked: false, + error: "", }, - args: {}, argTypes: {}, }; -export const Horizontal: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.classList.add("af-radio__container"); - container.innerHTML = `
- - - -
`; - - return container; +export const Select: StoryObj = { + render, + args: { + label: "Quelle ville ?", + isRequired: true, + error: "", + vertical: true, + disableParis: false, + disableBruxelles: false, + disableLille: true, + showIconParis: true, + showIconBruxelles: true, + showIconLille: true, + radioList: [ + { + name: "cities", + labelGroup: { + title: "Paris", + subtitle: "Capitale de la France", + description: "Nord", + }, + }, + { + name: "cities", + labelGroup: { title: "Bruxelles", subtitle: "Capitale de la Belgique" }, + }, + { + name: "cities", + labelGroup: { title: "Lille" }, + }, + ], }, - args: {}, - argTypes: {}, -}; - -export const HorizontalWithError: StoryObj = { - render: () => { - const container = document.createElement("div"); - container.classList.add("af-radio__container"); - container.innerHTML = `
- - -
- ${InputError}`; - - return container; + argTypes: { + radioList: { control: false }, + check: { + control: "inline-radio", + options: ["Paris", "Bruxelles", "Lille"], + }, }, - args: {}, - argTypes: {}, + decorators: [ + (story, { args }) => { + const { + check, + disableParis, + disableBruxelles, + disableLille, + showIconParis, + showIconBruxelles, + showIconLille, + radioList, + ...restArgs + } = args; + radioList[0] = { + ...radioList[0], + disabled: disableParis, + checked: check === "Paris", + withIcon: showIconParis, + }; + radioList[1] = { + ...radioList[1], + disabled: disableBruxelles, + checked: check === "Bruxelles", + withIcon: showIconBruxelles, + }; + radioList[2] = { + ...radioList[2], + disabled: disableLille, + checked: check === "Lille", + withIcon: showIconLille, + }; + return story({ args: { radioList, ...restArgs } }); + }, + ], }; diff --git a/client/look-and-feel/css/src/Form/Radio/render.ts b/client/look-and-feel/css/src/Form/Radio/render.ts new file mode 100644 index 000000000..7d00039b0 --- /dev/null +++ b/client/look-and-feel/css/src/Form/Radio/render.ts @@ -0,0 +1,161 @@ +import type { Args } from "@storybook/html"; +import { + createNode, + div, + input, + label, + span, + svg, + type Tattributes, + Tchildren, +} from "../../utils"; +import { TRadioValue } from "./Radio.stories"; + +export const getInput = (args: Args) => { + return input({ + id: args.id, + name: args.name, + type: "radio", + value: args.value, + disabled: args.disabled, + checked: args.checked, + } as Tattributes); +}; + +export const getIconDiv = () => { + const svgUnchecked = svg({ + class: "af-radio__unchecked", + path: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z", + }); + const svgChecked = svg({ + class: "af-radio__checked", + path: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z", + }); + + svgChecked.innerHTML += ``; + + const iconContainer = div([], { + class: "af-radio__icons", + }); + + iconContainer.innerHTML = `${svgUnchecked.outerHTML}${svgChecked.outerHTML}`; + return iconContainer; +}; + +export const getSpan = (spanLabel: string, attributes?: Tattributes) => { + return createNode({ node: "span", children: [spanLabel], attributes }); +}; + +export const getDescriptionDiv = (children: Tchildren) => { + return div(children, { + class: "af-radio__content-description", + }); +}; + +export const renderLabelBasic = (args: Args) => { + const inputRadio = getInput({ + id: `id-${args.name}`, + name: args.name, + error: !!args.error, + disabled: args.disabled && !args.error, + checked: args.checked && !args.error, + }); + const iconDiv = getIconDiv(); + return label([inputRadio, iconDiv, args.label], { + htmlFor: `id-${args.name}`, + }); +}; + +export const renderLabel = (args: Args, isError: boolean) => { + const inputRadio = getInput({ + id: `id-${args.name}`, + name: args.name, + error: isError, + disabled: args.disabled && !args.error, + checked: args.checked && !args.error, + }); + const iconDiv = getIconDiv(); + const description: (string | Node)[] = []; + + if (args.labelGroup.title) { + description.push(getSpan(args.labelGroup.title)); + } + if (args.labelGroup.subtitle) { + description.push(getSpan(args.labelGroup.subtitle)); + } + if (args.labelGroup.description) { + description.push(getSpan(args.labelGroup.description)); + } + const descriptionGroup = getDescriptionDiv(description); + + const group = div([], { + class: "af-radio__content", + }); + if (args.withIcon) { + const icon = svg({ + class: "af-radio__icon", + path: "M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z", + }); + group.innerHTML = `${icon.outerHTML}`; + } + group.innerHTML += `${(descriptionGroup as HTMLElement).outerHTML}`; + + return label([inputRadio, iconDiv, group]); +}; + +export const renderError = (error: string) => { + const errorDiv = div([], { class: "af-input-error", ariaLive: "assertive" }); + const icon = svg({ + path: "M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2M12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8", + class: "af-input-error__icon", + }); + const spanLabel = getSpan(error, { class: "af-input-error__message" }); + errorDiv.innerHTML = `${icon.outerHTML}${spanLabel.outerHTML}`; + return errorDiv; +}; +export const renderBasic = (args: Args) => { + const children = [ + div([renderLabelBasic(args)], { + class: "af-radio", + "aria-invalid": `${Boolean(args.error)}`, + }), + ]; + if (args.error) { + children.push(renderError(args.error)); + } + return div(children); +}; + +export const render = (args: Args) => { + const positionClass = args.vertical + ? "af-radio-select--vertical" + : "af-radio-select--horizontal"; + const attributes: Tattributes = { + role: "group", + class: `af-radio af-radio-select ${positionClass} ${args.class}`, + "aria-invalid": `${Boolean(args.error)}`, + }; + const radioLabels: (string | Node)[] = []; + args.radioList.forEach((radioLabel: TRadioValue) => { + radioLabels.push(renderLabel(radioLabel, !!args.error)); + }); + + const container = div(radioLabels, attributes); + const childrenContainer = []; + if (args.label) { + const spanLabelChildren: (string | Node)[] = [args.label]; + if (args.isRequired) { + spanLabelChildren.push(span([" *"], { ariaHidden: "true" })); + } + childrenContainer.push( + span(spanLabelChildren, { class: "af-radio__label" }), + ); + } + childrenContainer.push(container); + + if (args.error) { + childrenContainer.push(renderError(args.error)); + } + + return div(childrenContainer, { class: `af-radio__container ${args.class}` }); +}; diff --git a/client/look-and-feel/css/src/Grid/Grid-demo-example-form.scss b/client/look-and-feel/css/src/Grid/Grid-demo-example-form.scss index dd32583fe..a23fe4eac 100644 --- a/client/look-and-feel/css/src/Grid/Grid-demo-example-form.scss +++ b/client/look-and-feel/css/src/Grid/Grid-demo-example-form.scss @@ -18,6 +18,26 @@ main.example-form { &__fields { --row-gap: 2rem; + .af-radio__container { + --cols: 4; + + .af-radio-select > * { + --cols: 2; + } + } + + .af-checkbox__container { + --cols: 6; + + .af-checkbox-select { + --row-gap: 1rem; + + & > * { + --cols: 3; + } + } + } + .af-form__input-text { @media (width > #{common.$breakpoint-sm}) { --cols: 5; diff --git a/client/look-and-feel/css/src/Grid/renderExampleForm.ts b/client/look-and-feel/css/src/Grid/renderExampleForm.ts index 4308ab74c..8c1cb3794 100644 --- a/client/look-and-feel/css/src/Grid/renderExampleForm.ts +++ b/client/look-and-feel/css/src/Grid/renderExampleForm.ts @@ -8,6 +8,8 @@ import { render as renderHeader } from "../Layout/Header/render"; import { render as renderTitle } from "../Title/render"; import { render as renderButton } from "../Button/render"; import { render as renderText } from "../Form/Text/render"; +import { render as renderCheckbox } from "../Form/Checkbox/render"; +import { render as renderRadio } from "../Form/Radio/render"; function capitalizeFirstLetter(val: string) { return String(val).charAt(0).toUpperCase() + String(val).slice(1); @@ -37,10 +39,65 @@ const FIELDS = [ ]; const renderFields = () => { + const checkboxField = renderCheckbox({ + label: "Quelle ville ?", + class: "subgrid", + isRequired: true, + error: "", + checkboxList: [ + { + name: "Paris", + withIcon: true, + labelGroup: { + title: "Paris", + subtitle: "Capitale de la France", + description: "Nord", + }, + }, + { + name: "Bruxelles", + withIcon: true, + labelGroup: { title: "Bruxelles", subtitle: "Capitale de la Belgique" }, + }, + { + name: "Lille", + withIcon: true, + labelGroup: { title: "Lille" }, + }, + ], + }); + const radioField = renderRadio({ + label: "Quelle ville ?", + class: "subgrid", + isRequired: true, + error: "", + radioList: [ + { + name: "cities", + withIcon: true, + labelGroup: { + title: "Paris", + subtitle: "Capitale de la France", + description: "Nord", + }, + }, + { + name: "cities", + withIcon: true, + labelGroup: { title: "Bruxelles", subtitle: "Capitale de la Belgique" }, + }, + { + name: "cities", + withIcon: true, + labelGroup: { title: "Lille" }, + }, + ], + }); + const fields = FIELDS.map((field) => renderText(field)); return subgrid({ - children: [...fields], + children: [checkboxField, radioField, ...fields], attributes: { class: "form__fields" }, }); }; diff --git a/client/look-and-feel/css/src/utils/index.ts b/client/look-and-feel/css/src/utils/index.ts index 423c8c33a..66c47404d 100644 --- a/client/look-and-feel/css/src/utils/index.ts +++ b/client/look-and-feel/css/src/utils/index.ts @@ -90,6 +90,11 @@ export const label = ( export const input = (attributes?: Tattributes): HTMLElement => createNode({ node: "input", attributes }); +export const span = ( + children: Tchildren, + attributes?: Tattributes, +): HTMLElement => createNode({ node: "span", attributes, children }); + export const checkbox = (attributes?: Tattributes): HTMLElement => input({ type: "checkbox", ...attributes }); @@ -103,7 +108,7 @@ export const svg = ({ path, ...svgAttributes }: Tattributes): HTMLElement => { ...restSvgAttributes } = svgAttributes; - const svgElement = createNode({ + return createNode({ node: "svg", attributes: { focusable, @@ -113,8 +118,6 @@ export const svg = ({ path, ...svgAttributes }: Tattributes): HTMLElement => { }, children: [pathElement], }); - - return svgElement; }; export const grid = ({ diff --git a/client/look-and-feel/react/src/Accordion/__tests__/Accordion.test.tsx b/client/look-and-feel/react/src/Accordion/__tests__/Accordion.test.tsx index d92e4f457..330fbf2eb 100644 --- a/client/look-and-feel/react/src/Accordion/__tests__/Accordion.test.tsx +++ b/client/look-and-feel/react/src/Accordion/__tests__/Accordion.test.tsx @@ -79,7 +79,7 @@ describe("Accordion", () => { ); const summary = screen.getByText("Accordion Title").closest("summary"); - expect(summary).toHaveClass("af-accordion__summary--title-first"); + expect(summary).toHaveClass("af-accordion__summary title-first"); expect(screen.getByText("icon").parentElement).toHaveClass( "af-accordion__title-container", ); diff --git a/client/look-and-feel/react/src/AccordionCore/AccordionCore.tsx b/client/look-and-feel/react/src/AccordionCore/AccordionCore.tsx index 4027366bc..6cd0dea50 100644 --- a/client/look-and-feel/react/src/AccordionCore/AccordionCore.tsx +++ b/client/look-and-feel/react/src/AccordionCore/AccordionCore.tsx @@ -28,7 +28,7 @@ export const AccordionCore = ({ ...detailsProps }: AccordionProps) => { const componentClassName = useMemo( - () => getComponentClassName(className, classModifier, "af-accordion"), + () => getComponentClassName("af-accordion", className, classModifier), [classModifier, className], ); diff --git a/client/look-and-feel/react/src/AccordionCore/__tests__/AccordionCore.test.tsx b/client/look-and-feel/react/src/AccordionCore/__tests__/AccordionCore.test.tsx index 0710b9cba..8f7d3a3ff 100644 --- a/client/look-and-feel/react/src/AccordionCore/__tests__/AccordionCore.test.tsx +++ b/client/look-and-feel/react/src/AccordionCore/__tests__/AccordionCore.test.tsx @@ -42,7 +42,7 @@ describe("AccordionCore", () => { expect( screen.getByText("Accordion Content").closest("details"), - ).toHaveClass(`${className} ${className}--${classModifier}`); + ).toHaveClass(`af-accordion custom-class af-accordion--modifier`); }); it("renders Accordion with open details", () => { diff --git a/client/look-and-feel/react/src/Card/Card.tsx b/client/look-and-feel/react/src/Card/Card.tsx index e1dfbf2ea..9d524b5ac 100644 --- a/client/look-and-feel/react/src/Card/Card.tsx +++ b/client/look-and-feel/react/src/Card/Card.tsx @@ -14,7 +14,7 @@ export const Card = ({ ...otherProps }: CardProps) => { const componentClassName = useMemo( - () => getComponentClassName(className, classModifier, "af-card"), + () => getComponentClassName("af-card", className, classModifier), [className, classModifier], ); const Component = useMemo(() => (onClick ? "button" : "section"), [onClick]); diff --git a/client/look-and-feel/react/src/Card/__tests__/Card.test.tsx b/client/look-and-feel/react/src/Card/__tests__/Card.test.tsx index ce96765db..c5fdd45a5 100644 --- a/client/look-and-feel/react/src/Card/__tests__/Card.test.tsx +++ b/client/look-and-feel/react/src/Card/__tests__/Card.test.tsx @@ -24,7 +24,7 @@ describe("Card", () => { ); expect(screen.getByText("A card")).toHaveClass( - "custom-class custom-class--modifier", + "af-card custom-class af-card--modifier", ); }); diff --git a/client/look-and-feel/react/src/Divider/Divider.tsx b/client/look-and-feel/react/src/Divider/Divider.tsx index aec23e416..13bbd313a 100644 --- a/client/look-and-feel/react/src/Divider/Divider.tsx +++ b/client/look-and-feel/react/src/Divider/Divider.tsx @@ -8,7 +8,7 @@ type DividerProps = { export const Divider = ({ className, classModifier }: DividerProps) => { const componentClassName = useMemo( - () => getComponentClassName(className, classModifier, "af-divider"), + () => getComponentClassName("af-divider", className, classModifier), [className, classModifier], ); diff --git a/client/look-and-feel/react/src/Divider/__tests__/Divider.test.tsx b/client/look-and-feel/react/src/Divider/__tests__/Divider.test.tsx index e50740bc9..021f9b7d9 100644 --- a/client/look-and-feel/react/src/Divider/__tests__/Divider.test.tsx +++ b/client/look-and-feel/react/src/Divider/__tests__/Divider.test.tsx @@ -24,6 +24,8 @@ describe("Divider component", () => { it("should render with custom class name and class modifier", () => { render(); const dividerElement = screen.getByRole("separator"); - expect(dividerElement).toHaveClass("custom-class custom-class--modifier"); + expect(dividerElement).toHaveClass( + "af-divider custom-class af-divider--modifier", + ); }); }); diff --git a/client/look-and-feel/react/src/Form/Checkbox/CheckboxSelect.tsx b/client/look-and-feel/react/src/Form/Checkbox/CheckboxSelect.tsx index c404785aa..999971e60 100644 --- a/client/look-and-feel/react/src/Form/Checkbox/CheckboxSelect.tsx +++ b/client/look-and-feel/react/src/Form/Checkbox/CheckboxSelect.tsx @@ -2,10 +2,11 @@ import "@axa-fr/design-system-look-and-feel-css/dist/Form/Checkbox/Checkbox.scss import checkBoxIcon from "@material-symbols/svg-400/outlined/check_box-fill.svg"; import checkBoxOutlineBlankIcon from "@material-symbols/svg-400/outlined/check_box_outline_blank.svg"; import errorOutline from "@material-symbols/svg-400/outlined/error.svg"; -import React, { type ReactNode, useId } from "react"; +import React, { ComponentPropsWithRef, type ReactNode, useId } from "react"; import { Svg } from "../../Svg"; +import { getComponentClassName } from "../../utilities/helpers/getComponentClassName"; -type CheckboxProps = { +type CheckboxProps = ComponentPropsWithRef<"input"> & { type: "vertical" | "horizontal"; labelGroup?: string; descriptionGroup?: string; @@ -21,6 +22,7 @@ type CheckboxProps = { }; export const CheckboxSelect = ({ + className, labelGroup, descriptionGroup, isRequired, @@ -29,9 +31,17 @@ export const CheckboxSelect = ({ onChange, type = "vertical", }: CheckboxProps) => { + const componentClassName = getComponentClassName( + "af-checkbox__container", + className, + ); + const checkboxGroupClassName = getComponentClassName( + `af-checkbox af-checkbox-select af-checkbox-select--${type}`, + className, + ); const optionId = useId(); return ( -
+
{labelGroup && ( @@ -43,10 +53,7 @@ export const CheckboxSelect = ({ {descriptionGroup} )}
-
+
{options.map( ({ label, description, subtitle, icon, ...inputProps }) => (