From a6ff5eee45215effb3344cb5d631a27a7c0369aa Mon Sep 17 00:00:00 2001
From: Armand Philippot
Date: Fri, 22 Sep 2023 19:34:01 +0200
Subject: refactor(components): rewrite form components
---
.cspell/project-words.txt | 1 +
.../atoms/forms/boolean-field.module.scss | 5 -
.../atoms/forms/boolean-field.stories.tsx | 175 ------------
src/components/atoms/forms/boolean-field.test.tsx | 60 -----
src/components/atoms/forms/boolean-field.tsx | 44 ----
src/components/atoms/forms/field.stories.tsx | 257 ------------------
src/components/atoms/forms/field.test.tsx | 30 ---
src/components/atoms/forms/field.tsx | 111 --------
.../fields/boolean-field/boolean-field.module.scss | 7 +
.../fields/boolean-field/boolean-field.stories.tsx | 175 ++++++++++++
.../fields/boolean-field/boolean-field.test.tsx | 36 +++
.../forms/fields/boolean-field/boolean-field.tsx | 86 ++++++
.../atoms/forms/fields/boolean-field/index.ts | 1 +
.../atoms/forms/fields/checkbox/checkbox.test.tsx | 33 +++
.../atoms/forms/fields/checkbox/checkbox.tsx | 13 +
.../atoms/forms/fields/checkbox/index.ts | 1 +
.../atoms/forms/fields/fields.module.scss | 50 ++++
src/components/atoms/forms/fields/index.ts | 6 +
src/components/atoms/forms/fields/input/index.ts | 1 +
.../atoms/forms/fields/input/input.stories.tsx | 237 +++++++++++++++++
.../atoms/forms/fields/input/input.test.tsx | 34 +++
src/components/atoms/forms/fields/input/input.tsx | 72 +++++
src/components/atoms/forms/fields/radio/index.ts | 1 +
.../atoms/forms/fields/radio/radio.test.tsx | 28 ++
src/components/atoms/forms/fields/radio/radio.tsx | 13 +
src/components/atoms/forms/fields/select/index.ts | 1 +
.../atoms/forms/fields/select/select.stories.tsx | 143 ++++++++++
.../atoms/forms/fields/select/select.test.tsx | 43 +++
.../atoms/forms/fields/select/select.tsx | 76 ++++++
.../atoms/forms/fields/text-area/index.ts | 1 +
.../forms/fields/text-area/text-area.stories.tsx | 136 ++++++++++
.../forms/fields/text-area/text-area.test.tsx | 20 ++
.../atoms/forms/fields/text-area/text-area.tsx | 69 +++++
.../atoms/forms/fieldset/fieldset.module.scss | 17 ++
.../atoms/forms/fieldset/fieldset.stories.tsx | 63 +++++
.../atoms/forms/fieldset/fieldset.test.tsx | 35 +++
src/components/atoms/forms/fieldset/fieldset.tsx | 68 +++++
src/components/atoms/forms/fieldset/index.ts | 1 +
src/components/atoms/forms/form.test.tsx | 13 -
src/components/atoms/forms/form.tsx | 80 ------
src/components/atoms/forms/form/form.test.tsx | 13 +
src/components/atoms/forms/form/form.tsx | 28 ++
src/components/atoms/forms/form/index.ts | 1 +
src/components/atoms/forms/forms.module.scss | 53 ----
src/components/atoms/forms/index.ts | 6 +-
src/components/atoms/forms/label.module.scss | 18 --
src/components/atoms/forms/label.stories.tsx | 104 --------
src/components/atoms/forms/label.test.tsx | 9 -
src/components/atoms/forms/label.tsx | 40 ---
src/components/atoms/forms/label/index.ts | 1 +
src/components/atoms/forms/label/label.module.scss | 18 ++
src/components/atoms/forms/label/label.stories.tsx | 119 +++++++++
src/components/atoms/forms/label/label.test.tsx | 9 +
src/components/atoms/forms/label/label.tsx | 62 +++++
src/components/atoms/forms/legend/index.ts | 1 +
.../atoms/forms/legend/legend.module.scss | 6 +
.../atoms/forms/legend/legend.stories.tsx | 27 ++
src/components/atoms/forms/legend/legend.test.tsx | 17 ++
src/components/atoms/forms/legend/legend.tsx | 21 ++
src/components/atoms/forms/select.stories.tsx | 151 -----------
src/components/atoms/forms/select.test.tsx | 30 ---
src/components/atoms/forms/select.tsx | 79 ------
src/components/atoms/index.ts | 1 +
src/components/atoms/modal/index.ts | 1 +
src/components/atoms/modal/modal.module.scss | 66 +++++
src/components/atoms/modal/modal.stories.tsx | 59 +++++
src/components/atoms/modal/modal.test.tsx | 25 ++
src/components/atoms/modal/modal.tsx | 49 ++++
.../molecules/forms/ackee-toggle.fixture.tsx | 1 -
.../molecules/forms/ackee-toggle.module.scss | 6 -
.../molecules/forms/ackee-toggle.stories.tsx | 125 ---------
.../molecules/forms/ackee-toggle.test.tsx | 15 --
src/components/molecules/forms/ackee-toggle.tsx | 147 -----------
.../molecules/forms/fieldset.fixture.tsx | 6 -
.../molecules/forms/fieldset.module.scss | 61 -----
.../molecules/forms/fieldset.stories.tsx | 176 -------------
src/components/molecules/forms/fieldset.test.tsx | 22 --
src/components/molecules/forms/fieldset.tsx | 118 ---------
.../molecules/forms/flipping-label.module.scss | 63 -----
.../molecules/forms/flipping-label.stories.tsx | 96 -------
.../molecules/forms/flipping-label.test.tsx | 14 -
src/components/molecules/forms/flipping-label.tsx | 37 ---
.../flipping-label/flipping-label.module.scss | 63 +++++
.../flipping-label/flipping-label.stories.tsx | 96 +++++++
.../forms/flipping-label/flipping-label.test.tsx | 14 +
.../forms/flipping-label/flipping-label.tsx | 37 +++
.../molecules/forms/flipping-label/index.ts | 1 +
src/components/molecules/forms/index.ts | 8 +-
.../forms/labelled-boolean-field.fixture.tsx | 1 -
.../forms/labelled-boolean-field.module.scss | 15 --
.../forms/labelled-boolean-field.stories.tsx | 254 ------------------
.../forms/labelled-boolean-field.test.tsx | 37 ---
.../molecules/forms/labelled-boolean-field.tsx | 85 ------
.../molecules/forms/labelled-field.module.scss | 9 -
.../molecules/forms/labelled-field.stories.tsx | 293 ---------------------
.../molecules/forms/labelled-field.test.tsx | 19 --
src/components/molecules/forms/labelled-field.tsx | 49 ----
.../molecules/forms/labelled-field/index.ts | 1 +
.../labelled-field/labelled-field.module.scss | 22 ++
.../labelled-field/labelled-field.stories.tsx | 130 +++++++++
.../forms/labelled-field/labelled-field.test.tsx | 32 +++
.../forms/labelled-field/labelled-field.tsx | 63 +++++
.../molecules/forms/labelled-select.module.scss | 9 -
.../molecules/forms/labelled-select.stories.tsx | 236 -----------------
.../molecules/forms/labelled-select.test.tsx | 25 --
src/components/molecules/forms/labelled-select.tsx | 66 -----
.../molecules/forms/motion-toggle.fixture.tsx | 1 -
.../molecules/forms/motion-toggle.stories.tsx | 86 ------
.../molecules/forms/motion-toggle.test.tsx | 15 --
src/components/molecules/forms/motion-toggle.tsx | 118 ---------
.../molecules/forms/prism-theme-toggle.stories.tsx | 60 -----
.../molecules/forms/prism-theme-toggle.test.tsx | 13 -
.../molecules/forms/prism-theme-toggle.tsx | 118 ---------
.../molecules/forms/radio-group.fixture.tsx | 47 ----
.../molecules/forms/radio-group.module.scss | 112 --------
.../molecules/forms/radio-group.stories.tsx | 285 --------------------
.../molecules/forms/radio-group.test.tsx | 30 ---
src/components/molecules/forms/radio-group.tsx | 157 -----------
.../molecules/forms/radio-group/index.ts | 1 +
.../forms/radio-group/radio-group.fixture.tsx | 41 +++
.../forms/radio-group/radio-group.module.scss | 9 +
.../forms/radio-group/radio-group.stories.tsx | 75 ++++++
.../forms/radio-group/radio-group.test.tsx | 59 +++++
.../molecules/forms/radio-group/radio-group.tsx | 110 ++++++++
src/components/molecules/forms/switch/index.ts | 1 +
.../molecules/forms/switch/switch.module.scss | 105 ++++++++
.../molecules/forms/switch/switch.stories.tsx | 48 ++++
.../molecules/forms/switch/switch.test.tsx | 49 ++++
src/components/molecules/forms/switch/switch.tsx | 132 ++++++++++
.../molecules/forms/theme-toggle.stories.tsx | 60 -----
.../molecules/forms/theme-toggle.test.tsx | 13 -
src/components/molecules/forms/theme-toggle.tsx | 106 --------
src/components/molecules/index.ts | 2 +-
src/components/molecules/modals/index.ts | 2 -
src/components/molecules/modals/modal.module.scss | 34 ---
src/components/molecules/modals/modal.stories.tsx | 96 -------
src/components/molecules/modals/modal.test.tsx | 18 --
src/components/molecules/modals/modal.tsx | 88 -------
.../molecules/modals/tooltip.fixture.tsx | 4 -
.../molecules/modals/tooltip.module.scss | 46 ----
.../molecules/modals/tooltip.stories.tsx | 84 ------
src/components/molecules/modals/tooltip.test.tsx | 20 --
src/components/molecules/modals/tooltip.tsx | 67 -----
src/components/molecules/tooltip/index.ts | 1 +
.../molecules/tooltip/tooltip.module.scss | 72 +++++
.../molecules/tooltip/tooltip.stories.tsx | 42 +++
src/components/molecules/tooltip/tooltip.test.tsx | 39 +++
src/components/molecules/tooltip/tooltip.tsx | 92 +++++++
.../forms/ackee-toggle/ackee-toggle.fixture.tsx | 1 +
.../forms/ackee-toggle/ackee-toggle.stories.tsx | 47 ++++
.../forms/ackee-toggle/ackee-toggle.test.tsx | 15 ++
.../organisms/forms/ackee-toggle/ackee-toggle.tsx | 139 ++++++++++
.../organisms/forms/ackee-toggle/index.ts | 1 +
.../organisms/forms/comment-form.module.scss | 8 -
.../organisms/forms/comment-form.stories.tsx | 123 ---------
.../organisms/forms/comment-form.test.tsx | 23 --
src/components/organisms/forms/comment-form.tsx | 193 --------------
.../forms/comment-form/comment-form.module.scss | 18 ++
.../forms/comment-form/comment-form.stories.tsx | 123 +++++++++
.../forms/comment-form/comment-form.test.tsx | 23 ++
.../organisms/forms/comment-form/comment-form.tsx | 251 ++++++++++++++++++
.../organisms/forms/comment-form/index.ts | 1 +
.../organisms/forms/contact-form.module.scss | 8 -
.../organisms/forms/contact-form.stories.tsx | 65 -----
.../organisms/forms/contact-form.test.tsx | 48 ----
src/components/organisms/forms/contact-form.tsx | 154 -----------
.../forms/contact-form/contact-form.module.scss | 15 ++
.../forms/contact-form/contact-form.stories.tsx | 65 +++++
.../forms/contact-form/contact-form.test.tsx | 48 ++++
.../organisms/forms/contact-form/contact-form.tsx | 210 +++++++++++++++
.../organisms/forms/contact-form/index.ts | 1 +
src/components/organisms/forms/index.ts | 4 +
.../organisms/forms/motion-toggle/index.ts | 1 +
.../forms/motion-toggle/motion-toggle.fixture.tsx | 1 +
.../forms/motion-toggle/motion-toggle.stories.tsx | 47 ++++
.../forms/motion-toggle/motion-toggle.test.tsx | 15 ++
.../forms/motion-toggle/motion-toggle.tsx | 89 +++++++
.../organisms/forms/prism-theme-toggle/index.ts | 1 +
.../prism-theme-toggle.stories.tsx | 20 ++
.../prism-theme-toggle/prism-theme-toggle.test.tsx | 13 +
.../prism-theme-toggle/prism-theme-toggle.tsx | 85 ++++++
.../organisms/forms/search-form.module.scss | 58 ----
.../organisms/forms/search-form.stories.tsx | 65 -----
.../organisms/forms/search-form.test.tsx | 16 --
src/components/organisms/forms/search-form.tsx | 72 -----
.../organisms/forms/search-form/index.ts | 1 +
.../forms/search-form/search-form.module.scss | 67 +++++
.../forms/search-form/search-form.stories.tsx | 65 +++++
.../forms/search-form/search-form.test.tsx | 16 ++
.../organisms/forms/search-form/search-form.tsx | 98 +++++++
.../organisms/forms/theme-toggle/index.ts | 1 +
.../forms/theme-toggle/theme-toggle.stories.tsx | 20 ++
.../forms/theme-toggle/theme-toggle.test.tsx | 13 +
.../organisms/forms/theme-toggle/theme-toggle.tsx | 76 ++++++
src/components/organisms/layout/no-results.tsx | 2 +-
src/components/organisms/modals/search-modal.tsx | 13 +-
.../organisms/modals/settings-modal.module.scss | 26 +-
.../organisms/modals/settings-modal.stories.tsx | 4 +-
.../organisms/modals/settings-modal.test.tsx | 4 +-
src/components/organisms/modals/settings-modal.tsx | 60 ++---
.../organisms/toolbar/main-nav.stories.tsx | 2 +-
src/components/organisms/toolbar/main-nav.tsx | 6 +-
.../organisms/toolbar/search.stories.tsx | 2 +-
src/components/organisms/toolbar/search.tsx | 6 +-
.../organisms/toolbar/settings.stories.tsx | 2 +-
src/components/organisms/toolbar/settings.tsx | 19 +-
src/components/organisms/toolbar/toolbar.tsx | 1 -
src/components/templates/page/page-layout.tsx | 14 +-
src/i18n/en.json | 12 +-
src/i18n/fr.json | 12 +-
src/pages/404.tsx | 2 +-
src/pages/contact.tsx | 16 +-
212 files changed, 5011 insertions(+), 5883 deletions(-)
delete mode 100644 src/components/atoms/forms/boolean-field.module.scss
delete mode 100644 src/components/atoms/forms/boolean-field.stories.tsx
delete mode 100644 src/components/atoms/forms/boolean-field.test.tsx
delete mode 100644 src/components/atoms/forms/boolean-field.tsx
delete mode 100644 src/components/atoms/forms/field.stories.tsx
delete mode 100644 src/components/atoms/forms/field.test.tsx
delete mode 100644 src/components/atoms/forms/field.tsx
create mode 100644 src/components/atoms/forms/fields/boolean-field/boolean-field.module.scss
create mode 100644 src/components/atoms/forms/fields/boolean-field/boolean-field.stories.tsx
create mode 100644 src/components/atoms/forms/fields/boolean-field/boolean-field.test.tsx
create mode 100644 src/components/atoms/forms/fields/boolean-field/boolean-field.tsx
create mode 100644 src/components/atoms/forms/fields/boolean-field/index.ts
create mode 100644 src/components/atoms/forms/fields/checkbox/checkbox.test.tsx
create mode 100644 src/components/atoms/forms/fields/checkbox/checkbox.tsx
create mode 100644 src/components/atoms/forms/fields/checkbox/index.ts
create mode 100644 src/components/atoms/forms/fields/fields.module.scss
create mode 100644 src/components/atoms/forms/fields/index.ts
create mode 100644 src/components/atoms/forms/fields/input/index.ts
create mode 100644 src/components/atoms/forms/fields/input/input.stories.tsx
create mode 100644 src/components/atoms/forms/fields/input/input.test.tsx
create mode 100644 src/components/atoms/forms/fields/input/input.tsx
create mode 100644 src/components/atoms/forms/fields/radio/index.ts
create mode 100644 src/components/atoms/forms/fields/radio/radio.test.tsx
create mode 100644 src/components/atoms/forms/fields/radio/radio.tsx
create mode 100644 src/components/atoms/forms/fields/select/index.ts
create mode 100644 src/components/atoms/forms/fields/select/select.stories.tsx
create mode 100644 src/components/atoms/forms/fields/select/select.test.tsx
create mode 100644 src/components/atoms/forms/fields/select/select.tsx
create mode 100644 src/components/atoms/forms/fields/text-area/index.ts
create mode 100644 src/components/atoms/forms/fields/text-area/text-area.stories.tsx
create mode 100644 src/components/atoms/forms/fields/text-area/text-area.test.tsx
create mode 100644 src/components/atoms/forms/fields/text-area/text-area.tsx
create mode 100644 src/components/atoms/forms/fieldset/fieldset.module.scss
create mode 100644 src/components/atoms/forms/fieldset/fieldset.stories.tsx
create mode 100644 src/components/atoms/forms/fieldset/fieldset.test.tsx
create mode 100644 src/components/atoms/forms/fieldset/fieldset.tsx
create mode 100644 src/components/atoms/forms/fieldset/index.ts
delete mode 100644 src/components/atoms/forms/form.test.tsx
delete mode 100644 src/components/atoms/forms/form.tsx
create mode 100644 src/components/atoms/forms/form/form.test.tsx
create mode 100644 src/components/atoms/forms/form/form.tsx
create mode 100644 src/components/atoms/forms/form/index.ts
delete mode 100644 src/components/atoms/forms/forms.module.scss
delete mode 100644 src/components/atoms/forms/label.module.scss
delete mode 100644 src/components/atoms/forms/label.stories.tsx
delete mode 100644 src/components/atoms/forms/label.test.tsx
delete mode 100644 src/components/atoms/forms/label.tsx
create mode 100644 src/components/atoms/forms/label/index.ts
create mode 100644 src/components/atoms/forms/label/label.module.scss
create mode 100644 src/components/atoms/forms/label/label.stories.tsx
create mode 100644 src/components/atoms/forms/label/label.test.tsx
create mode 100644 src/components/atoms/forms/label/label.tsx
create mode 100644 src/components/atoms/forms/legend/index.ts
create mode 100644 src/components/atoms/forms/legend/legend.module.scss
create mode 100644 src/components/atoms/forms/legend/legend.stories.tsx
create mode 100644 src/components/atoms/forms/legend/legend.test.tsx
create mode 100644 src/components/atoms/forms/legend/legend.tsx
delete mode 100644 src/components/atoms/forms/select.stories.tsx
delete mode 100644 src/components/atoms/forms/select.test.tsx
delete mode 100644 src/components/atoms/forms/select.tsx
create mode 100644 src/components/atoms/modal/index.ts
create mode 100644 src/components/atoms/modal/modal.module.scss
create mode 100644 src/components/atoms/modal/modal.stories.tsx
create mode 100644 src/components/atoms/modal/modal.test.tsx
create mode 100644 src/components/atoms/modal/modal.tsx
delete mode 100644 src/components/molecules/forms/ackee-toggle.fixture.tsx
delete mode 100644 src/components/molecules/forms/ackee-toggle.module.scss
delete mode 100644 src/components/molecules/forms/ackee-toggle.stories.tsx
delete mode 100644 src/components/molecules/forms/ackee-toggle.test.tsx
delete mode 100644 src/components/molecules/forms/ackee-toggle.tsx
delete mode 100644 src/components/molecules/forms/fieldset.fixture.tsx
delete mode 100644 src/components/molecules/forms/fieldset.module.scss
delete mode 100644 src/components/molecules/forms/fieldset.stories.tsx
delete mode 100644 src/components/molecules/forms/fieldset.test.tsx
delete mode 100644 src/components/molecules/forms/fieldset.tsx
delete mode 100644 src/components/molecules/forms/flipping-label.module.scss
delete mode 100644 src/components/molecules/forms/flipping-label.stories.tsx
delete mode 100644 src/components/molecules/forms/flipping-label.test.tsx
delete mode 100644 src/components/molecules/forms/flipping-label.tsx
create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.module.scss
create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.stories.tsx
create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.test.tsx
create mode 100644 src/components/molecules/forms/flipping-label/flipping-label.tsx
create mode 100644 src/components/molecules/forms/flipping-label/index.ts
delete mode 100644 src/components/molecules/forms/labelled-boolean-field.fixture.tsx
delete mode 100644 src/components/molecules/forms/labelled-boolean-field.module.scss
delete mode 100644 src/components/molecules/forms/labelled-boolean-field.stories.tsx
delete mode 100644 src/components/molecules/forms/labelled-boolean-field.test.tsx
delete mode 100644 src/components/molecules/forms/labelled-boolean-field.tsx
delete mode 100644 src/components/molecules/forms/labelled-field.module.scss
delete mode 100644 src/components/molecules/forms/labelled-field.stories.tsx
delete mode 100644 src/components/molecules/forms/labelled-field.test.tsx
delete mode 100644 src/components/molecules/forms/labelled-field.tsx
create mode 100644 src/components/molecules/forms/labelled-field/index.ts
create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.module.scss
create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.stories.tsx
create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.test.tsx
create mode 100644 src/components/molecules/forms/labelled-field/labelled-field.tsx
delete mode 100644 src/components/molecules/forms/labelled-select.module.scss
delete mode 100644 src/components/molecules/forms/labelled-select.stories.tsx
delete mode 100644 src/components/molecules/forms/labelled-select.test.tsx
delete mode 100644 src/components/molecules/forms/labelled-select.tsx
delete mode 100644 src/components/molecules/forms/motion-toggle.fixture.tsx
delete mode 100644 src/components/molecules/forms/motion-toggle.stories.tsx
delete mode 100644 src/components/molecules/forms/motion-toggle.test.tsx
delete mode 100644 src/components/molecules/forms/motion-toggle.tsx
delete mode 100644 src/components/molecules/forms/prism-theme-toggle.stories.tsx
delete mode 100644 src/components/molecules/forms/prism-theme-toggle.test.tsx
delete mode 100644 src/components/molecules/forms/prism-theme-toggle.tsx
delete mode 100644 src/components/molecules/forms/radio-group.fixture.tsx
delete mode 100644 src/components/molecules/forms/radio-group.module.scss
delete mode 100644 src/components/molecules/forms/radio-group.stories.tsx
delete mode 100644 src/components/molecules/forms/radio-group.test.tsx
delete mode 100644 src/components/molecules/forms/radio-group.tsx
create mode 100644 src/components/molecules/forms/radio-group/index.ts
create mode 100644 src/components/molecules/forms/radio-group/radio-group.fixture.tsx
create mode 100644 src/components/molecules/forms/radio-group/radio-group.module.scss
create mode 100644 src/components/molecules/forms/radio-group/radio-group.stories.tsx
create mode 100644 src/components/molecules/forms/radio-group/radio-group.test.tsx
create mode 100644 src/components/molecules/forms/radio-group/radio-group.tsx
create mode 100644 src/components/molecules/forms/switch/index.ts
create mode 100644 src/components/molecules/forms/switch/switch.module.scss
create mode 100644 src/components/molecules/forms/switch/switch.stories.tsx
create mode 100644 src/components/molecules/forms/switch/switch.test.tsx
create mode 100644 src/components/molecules/forms/switch/switch.tsx
delete mode 100644 src/components/molecules/forms/theme-toggle.stories.tsx
delete mode 100644 src/components/molecules/forms/theme-toggle.test.tsx
delete mode 100644 src/components/molecules/forms/theme-toggle.tsx
delete mode 100644 src/components/molecules/modals/index.ts
delete mode 100644 src/components/molecules/modals/modal.module.scss
delete mode 100644 src/components/molecules/modals/modal.stories.tsx
delete mode 100644 src/components/molecules/modals/modal.test.tsx
delete mode 100644 src/components/molecules/modals/modal.tsx
delete mode 100644 src/components/molecules/modals/tooltip.fixture.tsx
delete mode 100644 src/components/molecules/modals/tooltip.module.scss
delete mode 100644 src/components/molecules/modals/tooltip.stories.tsx
delete mode 100644 src/components/molecules/modals/tooltip.test.tsx
delete mode 100644 src/components/molecules/modals/tooltip.tsx
create mode 100644 src/components/molecules/tooltip/index.ts
create mode 100644 src/components/molecules/tooltip/tooltip.module.scss
create mode 100644 src/components/molecules/tooltip/tooltip.stories.tsx
create mode 100644 src/components/molecules/tooltip/tooltip.test.tsx
create mode 100644 src/components/molecules/tooltip/tooltip.tsx
create mode 100644 src/components/organisms/forms/ackee-toggle/ackee-toggle.fixture.tsx
create mode 100644 src/components/organisms/forms/ackee-toggle/ackee-toggle.stories.tsx
create mode 100644 src/components/organisms/forms/ackee-toggle/ackee-toggle.test.tsx
create mode 100644 src/components/organisms/forms/ackee-toggle/ackee-toggle.tsx
create mode 100644 src/components/organisms/forms/ackee-toggle/index.ts
delete mode 100644 src/components/organisms/forms/comment-form.module.scss
delete mode 100644 src/components/organisms/forms/comment-form.stories.tsx
delete mode 100644 src/components/organisms/forms/comment-form.test.tsx
delete mode 100644 src/components/organisms/forms/comment-form.tsx
create mode 100644 src/components/organisms/forms/comment-form/comment-form.module.scss
create mode 100644 src/components/organisms/forms/comment-form/comment-form.stories.tsx
create mode 100644 src/components/organisms/forms/comment-form/comment-form.test.tsx
create mode 100644 src/components/organisms/forms/comment-form/comment-form.tsx
create mode 100644 src/components/organisms/forms/comment-form/index.ts
delete mode 100644 src/components/organisms/forms/contact-form.module.scss
delete mode 100644 src/components/organisms/forms/contact-form.stories.tsx
delete mode 100644 src/components/organisms/forms/contact-form.test.tsx
delete mode 100644 src/components/organisms/forms/contact-form.tsx
create mode 100644 src/components/organisms/forms/contact-form/contact-form.module.scss
create mode 100644 src/components/organisms/forms/contact-form/contact-form.stories.tsx
create mode 100644 src/components/organisms/forms/contact-form/contact-form.test.tsx
create mode 100644 src/components/organisms/forms/contact-form/contact-form.tsx
create mode 100644 src/components/organisms/forms/contact-form/index.ts
create mode 100644 src/components/organisms/forms/motion-toggle/index.ts
create mode 100644 src/components/organisms/forms/motion-toggle/motion-toggle.fixture.tsx
create mode 100644 src/components/organisms/forms/motion-toggle/motion-toggle.stories.tsx
create mode 100644 src/components/organisms/forms/motion-toggle/motion-toggle.test.tsx
create mode 100644 src/components/organisms/forms/motion-toggle/motion-toggle.tsx
create mode 100644 src/components/organisms/forms/prism-theme-toggle/index.ts
create mode 100644 src/components/organisms/forms/prism-theme-toggle/prism-theme-toggle.stories.tsx
create mode 100644 src/components/organisms/forms/prism-theme-toggle/prism-theme-toggle.test.tsx
create mode 100644 src/components/organisms/forms/prism-theme-toggle/prism-theme-toggle.tsx
delete mode 100644 src/components/organisms/forms/search-form.module.scss
delete mode 100644 src/components/organisms/forms/search-form.stories.tsx
delete mode 100644 src/components/organisms/forms/search-form.test.tsx
delete mode 100644 src/components/organisms/forms/search-form.tsx
create mode 100644 src/components/organisms/forms/search-form/index.ts
create mode 100644 src/components/organisms/forms/search-form/search-form.module.scss
create mode 100644 src/components/organisms/forms/search-form/search-form.stories.tsx
create mode 100644 src/components/organisms/forms/search-form/search-form.test.tsx
create mode 100644 src/components/organisms/forms/search-form/search-form.tsx
create mode 100644 src/components/organisms/forms/theme-toggle/index.ts
create mode 100644 src/components/organisms/forms/theme-toggle/theme-toggle.stories.tsx
create mode 100644 src/components/organisms/forms/theme-toggle/theme-toggle.test.tsx
create mode 100644 src/components/organisms/forms/theme-toggle/theme-toggle.tsx
diff --git a/.cspell/project-words.txt b/.cspell/project-words.txt
index 7c8eb22..9e5b1c3 100644
--- a/.cspell/project-words.txt
+++ b/.cspell/project-words.txt
@@ -16,3 +16,4 @@ rehype
SAMEORIGIN
stylelint
svgr
+thematics
diff --git a/src/components/atoms/forms/boolean-field.module.scss b/src/components/atoms/forms/boolean-field.module.scss
deleted file mode 100644
index f299ddd..0000000
--- a/src/components/atoms/forms/boolean-field.module.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@use "../../../styles/abstracts/mixins" as mix;
-
-.hidden {
- @include mix.visually-hidden;
-}
diff --git a/src/components/atoms/forms/boolean-field.stories.tsx b/src/components/atoms/forms/boolean-field.stories.tsx
deleted file mode 100644
index 3d6f8c3..0000000
--- a/src/components/atoms/forms/boolean-field.stories.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { useState } from 'react';
-import { BooleanField } from './boolean-field';
-
-/**
- * BooleanField - Storybook Meta
- */
-export default {
- title: 'Atoms/Forms',
- component: BooleanField,
- args: {
- hidden: false,
- },
- argTypes: {
- 'aria-labelledby': {
- control: {
- type: 'text',
- },
- description: 'One or more ids that refers to the field name.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- checked: {
- control: {
- type: null,
- },
- description: 'The field state: true if checked.',
- type: {
- name: 'boolean',
- required: true,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the field.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- hidden: {
- control: {
- type: 'boolean',
- },
- description: 'Define if the field should be visually hidden.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- id: {
- control: {
- type: 'text',
- },
- description: 'The field id.',
- type: {
- name: 'string',
- required: true,
- },
- },
- name: {
- control: {
- type: 'text',
- },
- description: 'The field name.',
- type: {
- name: 'string',
- required: true,
- },
- },
- onChange: {
- control: {
- type: null,
- },
- description: 'A callback function to handle field state change.',
- table: {
- category: 'Events',
- },
- type: {
- name: 'function',
- required: true,
- },
- },
- onClick: {
- control: {
- type: null,
- },
- description: 'A callback function to handle click on field.',
- table: {
- category: 'Events',
- },
- type: {
- name: 'function',
- required: false,
- },
- },
- type: {
- control: {
- type: 'select',
- },
- description: 'The field type. Either checkbox or radio.',
- options: ['checkbox', 'radio'],
- type: {
- name: 'string',
- required: true,
- },
- },
- value: {
- control: {
- type: 'text',
- },
- description: 'The field value.',
- type: {
- name: 'string',
- required: true,
- },
- },
- },
-} as ComponentMeta;
-
-const Template: ComponentStory = ({
- checked,
- onChange: _onChange,
- ...args
-}) => {
- const [isChecked, setIsChecked] = useState(checked);
-
- return (
- {
- setIsChecked(!isChecked);
- }}
- {...args}
- />
- );
-};
-
-/**
- * Checkbox Story
- */
-export const Checkbox = Template.bind({});
-Checkbox.args = {
- id: 'checkbox',
- checked: false,
- name: 'checkbox',
- type: 'checkbox',
- value: 'checkbox',
-};
-
-/**
- * Radio Story
- */
-export const Radio = Template.bind({});
-Radio.args = {
- id: 'radio',
- checked: false,
- name: 'radio',
- type: 'radio',
- value: 'radio',
-};
diff --git a/src/components/atoms/forms/boolean-field.test.tsx b/src/components/atoms/forms/boolean-field.test.tsx
deleted file mode 100644
index 503d1ce..0000000
--- a/src/components/atoms/forms/boolean-field.test.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import { render, screen } from '../../../../tests/utils';
-import { BooleanField } from './boolean-field';
-
-describe('BooleanField', () => {
- it('renders an unchecked checkbox', () => {
- render(
- null}
- type="checkbox"
- value="checkbox"
- />
- );
- expect(screen.getByRole('checkbox')).not.toBeChecked();
- });
-
- it('renders a checked checkbox', () => {
- render(
- null}
- type="checkbox"
- value="checkbox"
- />
- );
- expect(screen.getByRole('checkbox')).toBeChecked();
- });
-
- it('renders an unchecked radio', () => {
- render(
- null}
- type="radio"
- value="radio"
- />
- );
- expect(screen.getByRole('radio')).not.toBeChecked();
- });
-
- it('renders a checked radio', () => {
- render(
- null}
- type="radio"
- value="radio"
- />
- );
- expect(screen.getByRole('radio')).toBeChecked();
- });
-});
diff --git a/src/components/atoms/forms/boolean-field.tsx b/src/components/atoms/forms/boolean-field.tsx
deleted file mode 100644
index 8f33a42..0000000
--- a/src/components/atoms/forms/boolean-field.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { FC, InputHTMLAttributes } from 'react';
-import styles from './boolean-field.module.scss';
-
-export type BooleanFieldProps = Omit<
- InputHTMLAttributes,
- 'checked' | 'hidden' | 'name' | 'type' | 'value'
-> & {
- /**
- * True if the field should be checked.
- */
- checked: boolean;
- /**
- * True if the field should be visually hidden. Default: false.
- */
- hidden?: boolean;
- /**
- * Field name attribute.
- */
- name: string;
- /**
- * The input type.
- */
- type: 'checkbox' | 'radio';
- /**
- * Field name attribute.
- */
- value: string;
-};
-
-/**
- * BooleanField component
- *
- * Render a checkbox or a radio input type.
- */
-export const BooleanField: FC = ({
- className = '',
- hidden = false,
- ...props
-}) => {
- const modifier = hidden ? 'hidden' : '';
- const inputClass = `${styles[modifier]} ${className}`;
-
- return ;
-};
diff --git a/src/components/atoms/forms/field.stories.tsx b/src/components/atoms/forms/field.stories.tsx
deleted file mode 100644
index 27fd3be..0000000
--- a/src/components/atoms/forms/field.stories.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { useState } from 'react';
-import { Field } from './field';
-
-/**
- * Field - Storybook Meta
- */
-export default {
- title: 'Atoms/Forms/Fields',
- component: Field,
- args: {
- disabled: false,
- required: false,
- },
- argTypes: {
- 'aria-labelledby': {
- control: {
- type: 'text',
- },
- description: 'One or more ids that refers to the field name.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Add classnames to the field.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- disabled: {
- control: {
- type: 'boolean',
- },
- description: 'Field state: either enabled or disabled.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- id: {
- control: {
- type: 'text',
- },
- description: 'Field id.',
- type: {
- name: 'string',
- required: true,
- },
- },
- max: {
- control: {
- type: 'number',
- },
- description: 'Maximum value.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'number',
- required: false,
- },
- },
- min: {
- control: {
- type: 'number',
- },
- description: 'Minimum value.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'number',
- required: false,
- },
- },
- name: {
- control: {
- type: 'text',
- },
- description: 'Field name.',
- type: {
- name: 'string',
- required: true,
- },
- },
- placeholder: {
- control: {
- type: 'text',
- },
- description: 'A placeholder value.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- required: {
- control: {
- type: 'boolean',
- },
- description: 'Determine if the field is required.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- setValue: {
- control: {
- type: null,
- },
- description: 'Callback function to set field value.',
- table: {
- category: 'Events',
- },
- type: {
- name: 'function',
- required: true,
- },
- },
- step: {
- control: {
- type: 'number',
- },
- description: 'Field incremental values that are valid.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'number',
- required: false,
- },
- },
- type: {
- control: {
- type: 'select',
- },
- description: 'Field type: input type or textarea.',
- options: [
- 'datetime-local',
- 'email',
- 'number',
- 'search',
- 'tel',
- 'text',
- 'textarea',
- 'time',
- 'url',
- ],
- type: {
- name: 'string',
- required: true,
- },
- },
- value: {
- control: {
- type: null,
- },
- description: 'Field value.',
- type: {
- name: 'string',
- required: true,
- },
- },
- },
-} as ComponentMeta;
-
-const Template: ComponentStory = ({
- value: _value,
- setValue: _setValue,
- ...args
-}) => {
- const [value, setValue] = useState('');
-
- return ;
-};
-
-/**
- * Field Story - DateTime
- */
-export const DateTime = Template.bind({});
-DateTime.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'datetime-local',
-};
-
-/**
- * Field Story - Email
- */
-export const Email = Template.bind({});
-Email.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'email',
-};
-
-/**
- * Field Story - Text
- */
-export const Text = Template.bind({});
-Text.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'text',
-};
-
-/**
- * Field Story - Number
- */
-export const Number = Template.bind({});
-Number.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'number',
-};
-
-/**
- * Field Story - TextArea
- */
-export const TextArea = Template.bind({});
-TextArea.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'textarea',
-};
-
-/**
- * Field Story - Time
- */
-export const Time = Template.bind({});
-Time.args = {
- id: 'field-storybook',
- name: 'field-storybook',
- type: 'time',
-};
diff --git a/src/components/atoms/forms/field.test.tsx b/src/components/atoms/forms/field.test.tsx
deleted file mode 100644
index 492aa48..0000000
--- a/src/components/atoms/forms/field.test.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { render, screen } from '../../../../tests/utils';
-import { Field } from './field';
-
-describe('Field', () => {
- it('renders a text input', () => {
- render(
- null}
- />
- );
- expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
- });
-
- it('renders a search input', () => {
- render(
- null}
- />
- );
- expect(screen.getByRole('searchbox')).toHaveAttribute('type', 'search');
- });
-});
diff --git a/src/components/atoms/forms/field.tsx b/src/components/atoms/forms/field.tsx
deleted file mode 100644
index a4553e3..0000000
--- a/src/components/atoms/forms/field.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import {
- ChangeEvent,
- forwardRef,
- ForwardRefRenderFunction,
- SetStateAction,
-} from 'react';
-import styles from './forms.module.scss';
-
-export type FieldType =
- | 'datetime-local'
- | 'email'
- | 'number'
- | 'search'
- | 'tel'
- | 'text'
- | 'textarea'
- | 'time'
- | 'url';
-
-export type FieldProps = {
- /**
- * One or more ids that refers to the field name.
- */
- 'aria-labelledby'?: string;
- /**
- * Add classnames to the field.
- */
- className?: string;
- /**
- * Field state. Either enabled (false) or disabled (true).
- */
- disabled?: boolean;
- /**
- * Field id attribute.
- */
- id: string;
- /**
- * Field maximum value.
- */
- max?: number | string;
- /**
- * Field minimum value.
- */
- min?: number | string;
- /**
- * Field name attribute.
- */
- name: string;
- /**
- * Placeholder value.
- */
- placeholder?: string;
- /**
- * True if the field is required. Default: false.
- */
- required?: boolean;
- /**
- * Callback function to set field value.
- */
- setValue: (value: SetStateAction) => void;
- /**
- * Field incremental values that are valid.
- */
- step?: number | string;
- /**
- * Field type. Default: text.
- */
- type: FieldType;
- /**
- * Field value.
- */
- value: string;
-};
-
-const FieldWithRef: ForwardRefRenderFunction = (
- { className = '', setValue, type, ...props },
- ref
-) => {
- /**
- * Update select value when an option is selected.
- * @param e - The option change event.
- */
- const updateValue = (
- e: ChangeEvent
- ) => {
- setValue(e.target.value);
- };
-
- return type === 'textarea' ? (
-
- ) : (
-
- );
-};
-
-/**
- * Field component.
- *
- * Render either an input or a textarea.
- */
-export const Field = forwardRef(FieldWithRef);
diff --git a/src/components/atoms/forms/fields/boolean-field/boolean-field.module.scss b/src/components/atoms/forms/fields/boolean-field/boolean-field.module.scss
new file mode 100644
index 0000000..7e13e43
--- /dev/null
+++ b/src/components/atoms/forms/fields/boolean-field/boolean-field.module.scss
@@ -0,0 +1,7 @@
+@use "../../../../../styles/abstracts/mixins" as mix;
+
+.field {
+ &--hidden {
+ @include mix.visually-hidden;
+ }
+}
diff --git a/src/components/atoms/forms/fields/boolean-field/boolean-field.stories.tsx b/src/components/atoms/forms/fields/boolean-field/boolean-field.stories.tsx
new file mode 100644
index 0000000..cb017da
--- /dev/null
+++ b/src/components/atoms/forms/fields/boolean-field/boolean-field.stories.tsx
@@ -0,0 +1,175 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { useState } from 'react';
+import { BooleanField } from './boolean-field';
+
+/**
+ * BooleanField - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms/Fields',
+ component: BooleanField,
+ args: {
+ isHidden: false,
+ },
+ argTypes: {
+ 'aria-labelledby': {
+ control: {
+ type: 'text',
+ },
+ description: 'One or more ids that refers to the field name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Set additional classnames to the field.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'The field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isChecked: {
+ control: {
+ type: null,
+ },
+ description: 'The field state: true if checked.',
+ type: {
+ name: 'boolean',
+ required: true,
+ },
+ },
+ isHidden: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Define if the field should be visually hidden.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'The field name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ onChange: {
+ control: {
+ type: null,
+ },
+ description: 'A callback function to handle field state change.',
+ table: {
+ category: 'Events',
+ },
+ type: {
+ name: 'function',
+ required: true,
+ },
+ },
+ onClick: {
+ control: {
+ type: null,
+ },
+ description: 'A callback function to handle click on field.',
+ table: {
+ category: 'Events',
+ },
+ type: {
+ name: 'function',
+ required: false,
+ },
+ },
+ type: {
+ control: {
+ type: 'select',
+ },
+ description: 'The field type. Either checkbox or radio.',
+ options: ['checkbox', 'radio'],
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ value: {
+ control: {
+ type: 'text',
+ },
+ description: 'The field value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = ({
+ isChecked: checked,
+ onChange: _onChange,
+ ...args
+}) => {
+ const [isChecked, setIsChecked] = useState(checked);
+
+ return (
+ {
+ setIsChecked(!isChecked);
+ }}
+ {...args}
+ />
+ );
+};
+
+/**
+ * Checkbox Story
+ */
+export const Checkbox = Template.bind({});
+Checkbox.args = {
+ id: 'checkbox',
+ isChecked: false,
+ name: 'checkbox',
+ type: 'checkbox',
+ value: 'checkbox',
+};
+
+/**
+ * Radio Story
+ */
+export const Radio = Template.bind({});
+Radio.args = {
+ id: 'radio',
+ isChecked: false,
+ name: 'radio',
+ type: 'radio',
+ value: 'radio',
+};
diff --git a/src/components/atoms/forms/fields/boolean-field/boolean-field.test.tsx b/src/components/atoms/forms/fields/boolean-field/boolean-field.test.tsx
new file mode 100644
index 0000000..fcd15ad
--- /dev/null
+++ b/src/components/atoms/forms/fields/boolean-field/boolean-field.test.tsx
@@ -0,0 +1,36 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { BooleanField } from './boolean-field';
+
+const handleChange = () => {
+ /**
+ * Do nothing.
+ */
+};
+
+describe('boolean field', () => {
+ it('renders a checkbox', () => {
+ render(
+
+ );
+ expect(screen.getByRole('checkbox')).toBeInTheDocument();
+ });
+
+ it('renders a radio button', () => {
+ render(
+
+ );
+ expect(screen.getByRole('radio')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/atoms/forms/fields/boolean-field/boolean-field.tsx b/src/components/atoms/forms/fields/boolean-field/boolean-field.tsx
new file mode 100644
index 0000000..7985c0b
--- /dev/null
+++ b/src/components/atoms/forms/fields/boolean-field/boolean-field.tsx
@@ -0,0 +1,86 @@
+import { FC, InputHTMLAttributes } from 'react';
+import styles from './boolean-field.module.scss';
+
+export type BooleanFieldProps = Omit<
+ InputHTMLAttributes,
+ | 'checked'
+ | 'disabled'
+ | 'hidden'
+ | 'name'
+ | 'readOnly'
+ | 'required'
+ | 'type'
+ | 'value'
+> & {
+ /**
+ * Should the field be checked?
+ *
+ * @default false
+ */
+ isChecked?: boolean;
+ /**
+ * Should the field be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the field be visually hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Should the field be readonly?
+ *
+ * @default false
+ */
+ isReadOnly?: boolean;
+ /**
+ * Should the field be required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+ /**
+ * Field name attribute.
+ */
+ name: string;
+ /**
+ * The input type.
+ */
+ type: 'checkbox' | 'radio';
+ /**
+ * Field name attribute.
+ */
+ value: string;
+};
+
+/**
+ * BooleanField component
+ *
+ * Render a checkbox or a radio input type.
+ */
+export const BooleanField: FC = ({
+ className = '',
+ isChecked = false,
+ isDisabled = false,
+ isHidden = false,
+ isReadOnly = false,
+ isRequired = false,
+ ...props
+}) => {
+ const visibilityClass = isHidden ? styles['field--hidden'] : '';
+ const inputClass = `${visibilityClass} ${className}`;
+
+ return (
+
+ );
+};
diff --git a/src/components/atoms/forms/fields/boolean-field/index.ts b/src/components/atoms/forms/fields/boolean-field/index.ts
new file mode 100644
index 0000000..a49d77b
--- /dev/null
+++ b/src/components/atoms/forms/fields/boolean-field/index.ts
@@ -0,0 +1 @@
+export * from './boolean-field';
diff --git a/src/components/atoms/forms/fields/checkbox/checkbox.test.tsx b/src/components/atoms/forms/fields/checkbox/checkbox.test.tsx
new file mode 100644
index 0000000..658799a
--- /dev/null
+++ b/src/components/atoms/forms/fields/checkbox/checkbox.test.tsx
@@ -0,0 +1,33 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { Checkbox } from './checkbox';
+
+const doNothing = () => {
+ // Do nothing
+};
+
+describe('Checkbox', () => {
+ it('renders an unchecked checkbox', () => {
+ render(
+
+ );
+ expect(screen.getByRole('checkbox')).not.toBeChecked();
+ });
+
+ it('renders a checked checkbox', () => {
+ render(
+
+ );
+ expect(screen.getByRole('checkbox')).toBeChecked();
+ });
+});
diff --git a/src/components/atoms/forms/fields/checkbox/checkbox.tsx b/src/components/atoms/forms/fields/checkbox/checkbox.tsx
new file mode 100644
index 0000000..2ac3809
--- /dev/null
+++ b/src/components/atoms/forms/fields/checkbox/checkbox.tsx
@@ -0,0 +1,13 @@
+import { FC } from 'react';
+import { BooleanField, BooleanFieldProps } from '../boolean-field';
+
+export type CheckboxProps = Omit;
+
+/**
+ * Checkbox component
+ *
+ * Render a checkbox input type.
+ */
+export const Checkbox: FC = (props) => (
+
+);
diff --git a/src/components/atoms/forms/fields/checkbox/index.ts b/src/components/atoms/forms/fields/checkbox/index.ts
new file mode 100644
index 0000000..8d78b3e
--- /dev/null
+++ b/src/components/atoms/forms/fields/checkbox/index.ts
@@ -0,0 +1 @@
+export * from './checkbox';
diff --git a/src/components/atoms/forms/fields/fields.module.scss b/src/components/atoms/forms/fields/fields.module.scss
new file mode 100644
index 0000000..f09117d
--- /dev/null
+++ b/src/components/atoms/forms/fields/fields.module.scss
@@ -0,0 +1,50 @@
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/mixins" as mix;
+
+.field {
+ width: 100%;
+ padding: var(--spacing-2xs) var(--spacing-xs);
+ background: var(--color-bg-tertiary);
+ border: fun.convert-px(2) solid var(--color-border);
+ box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow);
+ transition: all 0.25s linear 0s;
+
+ &--select {
+ cursor: pointer;
+
+ @include mix.pointer("fine") {
+ padding: fun.convert-px(3) var(--spacing-xs);
+ }
+ }
+
+ &--textarea {
+ min-height: fun.convert-px(200);
+ }
+
+ &:disabled {
+ background: var(--color-bg-secondary);
+ border: fun.convert-px(2) solid var(--color-border-light);
+ box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0
+ var(--color-shadow-light);
+ cursor: not-allowed;
+ }
+
+ &:not(:disabled) {
+ &:hover {
+ box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1)
+ var(--color-shadow);
+ transform: translate(#{fun.convert-px(-3)}, #{fun.convert-px(-3)});
+ }
+
+ &:focus {
+ background: var(--color-bg);
+ border-color: var(--color-primary);
+ box-shadow: 0 0 0 0 var(--color-shadow);
+ transform: translate(#{fun.convert-px(3)}, #{fun.convert-px(3)});
+ outline: none;
+ transition:
+ all 0.2s ease-in-out 0s,
+ transform 0.3s ease-out 0s;
+ }
+ }
+}
diff --git a/src/components/atoms/forms/fields/index.ts b/src/components/atoms/forms/fields/index.ts
new file mode 100644
index 0000000..7fafba1
--- /dev/null
+++ b/src/components/atoms/forms/fields/index.ts
@@ -0,0 +1,6 @@
+export * from './boolean-field';
+export * from './checkbox';
+export * from './input';
+export * from './radio';
+export * from './select';
+export * from './text-area';
diff --git a/src/components/atoms/forms/fields/input/index.ts b/src/components/atoms/forms/fields/input/index.ts
new file mode 100644
index 0000000..e3365cb
--- /dev/null
+++ b/src/components/atoms/forms/fields/input/index.ts
@@ -0,0 +1 @@
+export * from './input';
diff --git a/src/components/atoms/forms/fields/input/input.stories.tsx b/src/components/atoms/forms/fields/input/input.stories.tsx
new file mode 100644
index 0000000..35657f8
--- /dev/null
+++ b/src/components/atoms/forms/fields/input/input.stories.tsx
@@ -0,0 +1,237 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { ChangeEvent, useCallback, useState } from 'react';
+import { Input } from './input';
+
+/**
+ * Input - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms/Fields',
+ component: Input,
+ args: {
+ isDisabled: false,
+ isRequired: false,
+ },
+ argTypes: {
+ 'aria-labelledby': {
+ control: {
+ type: 'text',
+ },
+ description: 'One or more ids that refers to the field name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add classnames to the field.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'Input id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isDisabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Input state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ isRequired: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ max: {
+ control: {
+ type: 'number',
+ },
+ description: 'Maximum value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ min: {
+ control: {
+ type: 'number',
+ },
+ description: 'Minimum value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'Input name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ placeholder: {
+ control: {
+ type: 'text',
+ },
+ description: 'A placeholder value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ step: {
+ control: {
+ type: 'number',
+ },
+ description: 'Input incremental values that are valid.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'number',
+ required: false,
+ },
+ },
+ type: {
+ control: {
+ type: 'select',
+ },
+ description: 'Input type: input type or textarea.',
+ options: [
+ 'datetime-local',
+ 'email',
+ 'number',
+ 'search',
+ 'tel',
+ 'text',
+ 'textarea',
+ 'time',
+ 'url',
+ ],
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ value: {
+ control: {
+ type: null,
+ },
+ description: 'Input value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = ({
+ value: initialValue,
+ onChange: _onChange,
+ ...args
+}) => {
+ const [value, setValue] = useState(initialValue);
+ const updateValue = useCallback((e: ChangeEvent) => {
+ setValue(e.target.value);
+ }, []);
+
+ return ;
+};
+
+/**
+ * Input Story - DateTime
+ */
+export const DateTime = Template.bind({});
+DateTime.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+ type: 'datetime-local',
+};
+
+/**
+ * Input Story - Email
+ */
+export const Email = Template.bind({});
+Email.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+ type: 'email',
+};
+
+/**
+ * Input Story - Numeric
+ */
+export const Numeric = Template.bind({});
+Numeric.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+ type: 'number',
+};
+
+/**
+ * Input Story - Text
+ */
+export const Text = Template.bind({});
+Text.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+ type: 'text',
+};
+
+/**
+ * Input Story - Time
+ */
+export const Time = Template.bind({});
+Time.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+ type: 'time',
+};
diff --git a/src/components/atoms/forms/fields/input/input.test.tsx b/src/components/atoms/forms/fields/input/input.test.tsx
new file mode 100644
index 0000000..1692c9e
--- /dev/null
+++ b/src/components/atoms/forms/fields/input/input.test.tsx
@@ -0,0 +1,34 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { Input } from './input';
+
+const doNothing = () => {
+ // do nothing
+};
+
+describe('Input', () => {
+ it('renders a text input', () => {
+ render(
+
+ );
+ expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
+ });
+
+ it('renders a search input', () => {
+ render(
+
+ );
+ expect(screen.getByRole('searchbox')).toHaveAttribute('type', 'search');
+ });
+});
diff --git a/src/components/atoms/forms/fields/input/input.tsx b/src/components/atoms/forms/fields/input/input.tsx
new file mode 100644
index 0000000..0f0736c
--- /dev/null
+++ b/src/components/atoms/forms/fields/input/input.tsx
@@ -0,0 +1,72 @@
+import {
+ type ForwardedRef,
+ forwardRef,
+ type InputHTMLAttributes,
+ type HTMLInputTypeAttribute,
+} from 'react';
+import styles from '../fields.module.scss';
+
+export type InputProps = Omit<
+ InputHTMLAttributes,
+ 'disabled' | 'hidden' | 'readonly' | 'required' | 'type'
+> &
+ Required, 'id' | 'name'>> & {
+ /**
+ * Should the field be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the field be hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Should the field be readonly?
+ *
+ * @default false
+ */
+ isReadOnly?: boolean;
+ /**
+ * Should the field be required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+ /**
+ * The input type.
+ */
+ type: Exclude;
+ };
+
+const InputWithRef = (
+ {
+ className = '',
+ isDisabled = false,
+ isHidden = false,
+ isReadOnly = false,
+ isRequired = false,
+ ...props
+ }: InputProps,
+ ref: ForwardedRef
+) => {
+ const fieldClassName = `${styles.field} ${className}`;
+
+ return (
+
+ );
+};
+
+/**
+ * Input component.
+ */
+export const Input = forwardRef(InputWithRef);
diff --git a/src/components/atoms/forms/fields/radio/index.ts b/src/components/atoms/forms/fields/radio/index.ts
new file mode 100644
index 0000000..1140e08
--- /dev/null
+++ b/src/components/atoms/forms/fields/radio/index.ts
@@ -0,0 +1 @@
+export * from './radio';
diff --git a/src/components/atoms/forms/fields/radio/radio.test.tsx b/src/components/atoms/forms/fields/radio/radio.test.tsx
new file mode 100644
index 0000000..42df991
--- /dev/null
+++ b/src/components/atoms/forms/fields/radio/radio.test.tsx
@@ -0,0 +1,28 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { Radio } from './radio';
+
+const doNothing = () => {
+ // Do nothing
+};
+
+describe('Radio', () => {
+ it('renders an unchecked radio', () => {
+ render(
+
+ );
+ expect(screen.getByRole('radio')).not.toBeChecked();
+ });
+
+ it('renders a checked radio', () => {
+ render(
+
+ );
+ expect(screen.getByRole('radio')).toBeChecked();
+ });
+});
diff --git a/src/components/atoms/forms/fields/radio/radio.tsx b/src/components/atoms/forms/fields/radio/radio.tsx
new file mode 100644
index 0000000..6430b4a
--- /dev/null
+++ b/src/components/atoms/forms/fields/radio/radio.tsx
@@ -0,0 +1,13 @@
+import { FC } from 'react';
+import { BooleanField, BooleanFieldProps } from '../boolean-field';
+
+export type RadioProps = Omit;
+
+/**
+ * Radio component
+ *
+ * Render a radio input type.
+ */
+export const Radio: FC = (props) => (
+
+);
diff --git a/src/components/atoms/forms/fields/select/index.ts b/src/components/atoms/forms/fields/select/index.ts
new file mode 100644
index 0000000..c739673
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/index.ts
@@ -0,0 +1 @@
+export * from './select';
diff --git a/src/components/atoms/forms/fields/select/select.stories.tsx b/src/components/atoms/forms/fields/select/select.stories.tsx
new file mode 100644
index 0000000..c9e02d2
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.stories.tsx
@@ -0,0 +1,143 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { ChangeEvent, useCallback, useState } from 'react';
+import { Select as SelectComponent } from './select';
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+
+/**
+ * Select - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms/Fields',
+ component: SelectComponent,
+ args: {
+ isDisabled: false,
+ isRequired: false,
+ },
+ argTypes: {
+ 'aria-labelledby': {
+ control: {
+ type: 'text',
+ },
+ description: 'One or more ids that refers to the select field name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add classnames to the select field.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isDisabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Field state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ isRequired: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'Field name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ options: {
+ description: 'Select options.',
+ type: {
+ name: 'array',
+ required: true,
+ value: {
+ name: 'string',
+ },
+ },
+ },
+ value: {
+ control: {
+ type: null,
+ },
+ description: 'Field value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = ({
+ onChange: _onChange,
+ value,
+ ...args
+}) => {
+ const [selected, setSelected] = useState(value);
+ const updateSelection = useCallback((e: ChangeEvent) => {
+ setSelected(e.target.value);
+ }, []);
+
+ return (
+
+ );
+};
+
+/**
+ * Select Story
+ */
+export const Select = Template.bind({});
+Select.args = {
+ id: 'storybook-select',
+ name: 'storybook-select',
+ options: selectOptions,
+ value: 'option2',
+};
diff --git a/src/components/atoms/forms/fields/select/select.test.tsx b/src/components/atoms/forms/fields/select/select.test.tsx
new file mode 100644
index 0000000..088cc9e
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.test.tsx
@@ -0,0 +1,43 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { Select } from './select';
+
+const doNothing = () => {
+ // do nothing
+};
+
+const selectOptions = [
+ { id: 'option1', name: 'Option 1', value: 'option1' },
+ { id: 'option2', name: 'Option 2', value: 'option2' },
+ { id: 'option3', name: 'Option 3', value: 'option3' },
+];
+const selected = selectOptions[0];
+
+describe('Select', () => {
+ it('should correctly set default option', () => {
+ render(
+
+ );
+
+ expect(screen.getByRole('combobox')).toHaveValue(selected.value);
+ });
+
+ it('renders the select options', () => {
+ render(
+
+ );
+
+ expect(screen.getAllByRole('option')).toHaveLength(selectOptions.length);
+ });
+});
diff --git a/src/components/atoms/forms/fields/select/select.tsx b/src/components/atoms/forms/fields/select/select.tsx
new file mode 100644
index 0000000..887dacc
--- /dev/null
+++ b/src/components/atoms/forms/fields/select/select.tsx
@@ -0,0 +1,76 @@
+import { FC, SelectHTMLAttributes } from 'react';
+import styles from '../fields.module.scss';
+
+export type SelectOptions = {
+ /**
+ * The option id.
+ */
+ id: string;
+ /**
+ * The option name.
+ */
+ name: string;
+ /**
+ * The option value.
+ */
+ value: string;
+};
+
+export type SelectProps = Omit<
+ SelectHTMLAttributes,
+ 'disabled' | 'hidden' | 'required'
+> & {
+ /**
+ * Should the select field be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the select field be hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Is the select field required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+ /**
+ * True if the field is required. Default: false.
+ */
+ options: SelectOptions[];
+};
+
+/**
+ * Select component
+ *
+ * Render a HTML select element.
+ */
+export const Select: FC = ({
+ className = '',
+ isDisabled = false,
+ isHidden = false,
+ isRequired = false,
+ options,
+ ...props
+}) => {
+ const selectClass = `${styles.field} ${styles['field--select']} ${className}`;
+
+ return (
+
+ );
+};
diff --git a/src/components/atoms/forms/fields/text-area/index.ts b/src/components/atoms/forms/fields/text-area/index.ts
new file mode 100644
index 0000000..e18b325
--- /dev/null
+++ b/src/components/atoms/forms/fields/text-area/index.ts
@@ -0,0 +1 @@
+export * from './text-area';
diff --git a/src/components/atoms/forms/fields/text-area/text-area.stories.tsx b/src/components/atoms/forms/fields/text-area/text-area.stories.tsx
new file mode 100644
index 0000000..2e77cb7
--- /dev/null
+++ b/src/components/atoms/forms/fields/text-area/text-area.stories.tsx
@@ -0,0 +1,136 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { ChangeEvent, useCallback, useState } from 'react';
+import { TextArea as TextAreaComponent } from './text-area';
+
+/**
+ * TextArea - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms/Fields',
+ component: TextAreaComponent,
+ args: {
+ isDisabled: false,
+ isRequired: false,
+ },
+ argTypes: {
+ 'aria-labelledby': {
+ control: {
+ type: 'text',
+ },
+ description: 'One or more ids that refers to the field name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add classnames to the field.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ id: {
+ control: {
+ type: 'text',
+ },
+ description: 'TextArea id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isDisabled: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'TextArea state: either enabled or disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ isRequired: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Determine if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ name: {
+ control: {
+ type: 'text',
+ },
+ description: 'TextArea name.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ placeholder: {
+ control: {
+ type: 'text',
+ },
+ description: 'A placeholder value.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ value: {
+ control: {
+ type: null,
+ },
+ description: 'TextArea value.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = ({
+ value: initialValue,
+ onChange: _onChange,
+ ...args
+}) => {
+ const [value, setValue] = useState(initialValue);
+ const updateValue = useCallback((e: ChangeEvent) => {
+ setValue(e.target.value);
+ }, []);
+
+ return ;
+};
+
+/**
+ * TextArea Story - TextArea
+ */
+export const TextArea = Template.bind({});
+TextArea.args = {
+ id: 'field-storybook',
+ name: 'field-storybook',
+};
diff --git a/src/components/atoms/forms/fields/text-area/text-area.test.tsx b/src/components/atoms/forms/fields/text-area/text-area.test.tsx
new file mode 100644
index 0000000..37a1d1c
--- /dev/null
+++ b/src/components/atoms/forms/fields/text-area/text-area.test.tsx
@@ -0,0 +1,20 @@
+import { render, screen } from '../../../../../../tests/utils';
+import { TextArea } from './text-area';
+
+const doNothing = () => {
+ // do nothing
+};
+
+describe('TextArea', () => {
+ it('renders a textarea', () => {
+ render(
+
+ );
+ expect(screen.getByRole('textbox')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/atoms/forms/fields/text-area/text-area.tsx b/src/components/atoms/forms/fields/text-area/text-area.tsx
new file mode 100644
index 0000000..bd99d7d
--- /dev/null
+++ b/src/components/atoms/forms/fields/text-area/text-area.tsx
@@ -0,0 +1,69 @@
+import {
+ type ForwardedRef,
+ forwardRef,
+ type TextareaHTMLAttributes,
+} from 'react';
+import styles from '../fields.module.scss';
+
+type AllowedTextAreaProps = Omit<
+ TextareaHTMLAttributes,
+ 'disabled' | 'readOnly' | 'required'
+> &
+ Required, 'id' | 'name'>>;
+
+export type TextAreaProps = AllowedTextAreaProps & {
+ /**
+ * Should the field be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the field be hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Should the field be readonly?
+ *
+ * @default false
+ */
+ isReadOnly?: boolean;
+ /**
+ * Should the field be required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+};
+
+const TextAreaWithRef = (
+ {
+ className = '',
+ isDisabled = false,
+ isHidden = false,
+ isReadOnly = false,
+ isRequired = false,
+ ...props
+ }: TextAreaProps,
+ ref: ForwardedRef
+) => {
+ const fieldClassName = `${styles.field} ${styles['field--textarea']} ${className}`;
+
+ return (
+
+ );
+};
+
+/**
+ * TextArea component.
+ */
+export const TextArea = forwardRef(TextAreaWithRef);
diff --git a/src/components/atoms/forms/fieldset/fieldset.module.scss b/src/components/atoms/forms/fieldset/fieldset.module.scss
new file mode 100644
index 0000000..ed545a7
--- /dev/null
+++ b/src/components/atoms/forms/fieldset/fieldset.module.scss
@@ -0,0 +1,17 @@
+.fieldset {
+ display: flex;
+ gap: var(--spacing-2xs);
+ max-width: 100%;
+ margin: 0;
+ padding: 0;
+ border: none;
+
+ &--inline {
+ flex-flow: row wrap;
+ align-items: center;
+ }
+
+ &--stack {
+ flex-flow: column wrap;
+ }
+}
diff --git a/src/components/atoms/forms/fieldset/fieldset.stories.tsx b/src/components/atoms/forms/fieldset/fieldset.stories.tsx
new file mode 100644
index 0000000..faf355f
--- /dev/null
+++ b/src/components/atoms/forms/fieldset/fieldset.stories.tsx
@@ -0,0 +1,63 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Fieldset as FieldsetComponent } from './fieldset';
+import { Input } from '../fields';
+import { Legend } from '../legend';
+
+/**
+ * Fieldset - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms',
+ component: FieldsetComponent,
+ args: {
+ isDisabled: false,
+ },
+ argTypes: {
+ isDisabled: {
+ control: {
+ type: 'boolean',
+ },
+ description:
+ 'Define if the fields inside the fieldset should be disabled.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => {
+ return (
+
+
+
+
+
+
+
+
+ );
+};
+
+/**
+ * Fieldset Story
+ */
+export const Fieldset = Template.bind({});
+Fieldset.args = {
+ legend: ,
+};
diff --git a/src/components/atoms/forms/fieldset/fieldset.test.tsx b/src/components/atoms/forms/fieldset/fieldset.test.tsx
new file mode 100644
index 0000000..08a0aaa
--- /dev/null
+++ b/src/components/atoms/forms/fieldset/fieldset.test.tsx
@@ -0,0 +1,35 @@
+import { render, screen } from '../../../../../tests/utils';
+import { Input } from '../fields';
+import { Fieldset } from './fieldset';
+
+describe('fieldset', () => {
+ it('renders a fieldset', () => {
+ render(
+
+ );
+ expect(screen.getByRole('group')).toBeInTheDocument();
+ expect(screen.getByRole('textbox')).not.toBeDisabled();
+ });
+
+ it('renders a disabled fieldset', () => {
+ render(
+
+ );
+ expect(screen.getByRole('group')).toBeInTheDocument();
+ expect(screen.getByRole('textbox')).toBeDisabled();
+ });
+});
diff --git a/src/components/atoms/forms/fieldset/fieldset.tsx b/src/components/atoms/forms/fieldset/fieldset.tsx
new file mode 100644
index 0000000..eb42961
--- /dev/null
+++ b/src/components/atoms/forms/fieldset/fieldset.tsx
@@ -0,0 +1,68 @@
+import {
+ forwardRef,
+ type FieldsetHTMLAttributes,
+ ForwardRefRenderFunction,
+ ReactElement,
+} from 'react';
+import styles from './fieldset.module.scss';
+import { LegendProps } from '../legend';
+
+export type FieldsetProps = Omit<
+ FieldsetHTMLAttributes,
+ 'disabled' | 'hidden'
+> & {
+ /**
+ * Should the fieldset be disabled?
+ *
+ * @default false
+ */
+ isDisabled?: boolean;
+ /**
+ * Should the fieldset contents be inlined?
+ *
+ * @default false
+ */
+ isInline?: boolean;
+ /**
+ * The fieldset legend.
+ */
+ legend?: ReactElement;
+};
+
+/**
+ * Fieldset component.
+ */
+const FieldsetWithRef: ForwardRefRenderFunction<
+ HTMLFieldSetElement,
+ FieldsetProps
+> = (
+ {
+ children,
+ className = '',
+ isDisabled = false,
+ isInline = false,
+ legend,
+ ...props
+ },
+ ref
+) => {
+ const layoutModifier = isInline
+ ? styles['fieldset--inline']
+ : styles['fieldset--stack'];
+ const legendModifier = legend ? styles['fieldset--has-legend'] : '';
+ const fieldsetClass = `${styles.fieldset} ${legendModifier} ${layoutModifier} ${className}`;
+
+ return (
+
+ );
+};
+
+export const Fieldset = forwardRef(FieldsetWithRef);
diff --git a/src/components/atoms/forms/fieldset/index.ts b/src/components/atoms/forms/fieldset/index.ts
new file mode 100644
index 0000000..00ef1f8
--- /dev/null
+++ b/src/components/atoms/forms/fieldset/index.ts
@@ -0,0 +1 @@
+export * from './fieldset';
diff --git a/src/components/atoms/forms/form.test.tsx b/src/components/atoms/forms/form.test.tsx
deleted file mode 100644
index b040665..0000000
--- a/src/components/atoms/forms/form.test.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { render, screen } from '../../../../tests/utils';
-import { Form } from './form';
-
-describe('Form', () => {
- it('renders a form', () => {
- render(
-
- );
- expect(screen.getByRole('form', { name: 'Jest form' })).toBeInTheDocument();
- });
-});
diff --git a/src/components/atoms/forms/form.tsx b/src/components/atoms/forms/form.tsx
deleted file mode 100644
index 85ff8fd..0000000
--- a/src/components/atoms/forms/form.tsx
+++ /dev/null
@@ -1,80 +0,0 @@
-import {
- Children,
- FC,
- FormEvent,
- FormHTMLAttributes,
- Fragment,
- ReactNode,
-} from 'react';
-import styles from './forms.module.scss';
-
-export type FormProps = Omit<
- FormHTMLAttributes,
- 'onSubmit'
-> & {
- /**
- * The form body.
- */
- children: ReactNode;
- /**
- * Wrap each items with a div. Default: true.
- */
- grouped?: boolean;
- /**
- * If grouped, set additional classnames to the items wrapper.
- */
- itemsClassName?: string;
- /**
- * A callback function to execute on submit.
- */
- onSubmit: () => void;
-};
-
-/**
- * Form component.
- *
- * Render children wrapped in a form element.
- */
-export const Form: FC = ({
- children,
- grouped = true,
- itemsClassName = '',
- onSubmit,
- ...props
-}) => {
- const arrayChildren = Children.toArray(children);
-
- /**
- * Get the form items.
- * @returns {JSX.Element[]} An array of child elements wrapped in a div.
- */
- const getFormItems = (): JSX.Element[] => {
- return arrayChildren.map((child, index) =>
- grouped ? (
-
- {child}
-
- ) : (
- {child}
- )
- );
- };
-
- /**
- * Handle form submit.
- * @param {FormEvent} e - The form event.
- */
- const handleSubmit = (e: FormEvent) => {
- e.preventDefault();
- onSubmit();
- };
-
- return (
-
- );
-};
diff --git a/src/components/atoms/forms/form/form.test.tsx b/src/components/atoms/forms/form/form.test.tsx
new file mode 100644
index 0000000..08165f5
--- /dev/null
+++ b/src/components/atoms/forms/form/form.test.tsx
@@ -0,0 +1,13 @@
+import { render, screen } from '../../../../../tests/utils';
+import { Form } from './form';
+
+describe('Form', () => {
+ it('renders a form', () => {
+ render(
+
+ );
+ expect(screen.getByRole('form')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/atoms/forms/form/form.tsx b/src/components/atoms/forms/form/form.tsx
new file mode 100644
index 0000000..86481d2
--- /dev/null
+++ b/src/components/atoms/forms/form/form.tsx
@@ -0,0 +1,28 @@
+import {
+ type FormHTMLAttributes,
+ forwardRef,
+ type ForwardRefRenderFunction,
+} from 'react';
+
+export type FormRole = 'form' | 'search' | 'none' | 'presentation';
+
+export type FormProps = FormHTMLAttributes & {
+ /**
+ * An accessible role.
+ */
+ role?: FormRole;
+};
+
+const FormWithRef: ForwardRefRenderFunction = (
+ { children, ...props },
+ ref
+) => (
+
+);
+
+/**
+ * Form component.
+ */
+export const Form = forwardRef(FormWithRef);
diff --git a/src/components/atoms/forms/form/index.ts b/src/components/atoms/forms/form/index.ts
new file mode 100644
index 0000000..698d687
--- /dev/null
+++ b/src/components/atoms/forms/form/index.ts
@@ -0,0 +1 @@
+export * from './form';
diff --git a/src/components/atoms/forms/forms.module.scss b/src/components/atoms/forms/forms.module.scss
deleted file mode 100644
index ece26e5..0000000
--- a/src/components/atoms/forms/forms.module.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@use "../../../styles/abstracts/functions" as fun;
-@use "../../../styles/abstracts/mixins" as mix;
-
-.item {
- margin: var(--spacing-xs) 0;
- width: 100%;
- max-width: 45ch;
-}
-
-.field {
- padding: var(--spacing-2xs) var(--spacing-xs);
- background: var(--color-bg-tertiary);
- border: fun.convert-px(2) solid var(--color-border);
- box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0 var(--color-shadow);
- transition: all 0.25s linear 0s;
-
- &--select {
- cursor: pointer;
-
- @include mix.pointer("fine") {
- padding: fun.convert-px(3) var(--spacing-xs);
- }
- }
-
- &--textarea {
- min-height: fun.convert-px(200);
- }
-
- &:disabled {
- background: var(--color-bg-secondary);
- border: fun.convert-px(2) solid var(--color-border-light);
- box-shadow: fun.convert-px(3) fun.convert-px(3) 0 0
- var(--color-shadow-light);
- cursor: not-allowed;
- }
-
- &:not(:disabled) {
- &:hover {
- box-shadow: fun.convert-px(5) fun.convert-px(5) 0 fun.convert-px(1)
- var(--color-shadow);
- transform: translate(#{fun.convert-px(-3)}, #{fun.convert-px(-3)});
- }
-
- &:focus {
- background: var(--color-bg);
- border-color: var(--color-primary);
- box-shadow: 0 0 0 0 var(--color-shadow);
- transform: translate(#{fun.convert-px(3)}, #{fun.convert-px(3)});
- outline: none;
- transition: all 0.2s ease-in-out 0s, transform 0.3s ease-out 0s;
- }
- }
-}
diff --git a/src/components/atoms/forms/index.ts b/src/components/atoms/forms/index.ts
index 0af138f..7e444c2 100644
--- a/src/components/atoms/forms/index.ts
+++ b/src/components/atoms/forms/index.ts
@@ -1,5 +1,5 @@
-export * from './boolean-field';
-export * from './field';
+export * from './fields';
+export * from './fieldset';
export * from './form';
export * from './label';
-export * from './select';
+export * from './legend';
diff --git a/src/components/atoms/forms/label.module.scss b/src/components/atoms/forms/label.module.scss
deleted file mode 100644
index aed1546..0000000
--- a/src/components/atoms/forms/label.module.scss
+++ /dev/null
@@ -1,18 +0,0 @@
-.label {
- color: var(--color-primary-darker);
- font-weight: 600;
- cursor: pointer;
-
- &--small {
- font-size: var(--font-size-sm);
- font-variant: small-caps;
- }
-
- &--medium {
- font-size: var(--font-size-md);
- }
-}
-
-.required {
- color: var(--color-secondary);
-}
diff --git a/src/components/atoms/forms/label.stories.tsx b/src/components/atoms/forms/label.stories.tsx
deleted file mode 100644
index 3adc92a..0000000
--- a/src/components/atoms/forms/label.stories.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { Label as LabelComponent } from './label';
-
-/**
- * Label - Storybook Meta
- */
-export default {
- title: 'Atoms/Forms',
- component: LabelComponent,
- args: {
- required: false,
- size: 'small',
- },
- argTypes: {
- 'aria-label': {
- control: {
- type: 'text',
- },
- description: 'Define an accessible name.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Add classnames to the label.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- children: {
- control: {
- type: 'text',
- },
- description: 'The label body.',
- type: {
- name: 'string',
- required: true,
- },
- },
- htmlFor: {
- control: {
- type: 'text',
- },
- description: 'The field id.',
- type: {
- name: 'string',
- required: true,
- },
- },
- required: {
- control: {
- type: 'boolean',
- },
- description: 'Set to true if the field is required.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- size: {
- control: {
- type: 'select',
- },
- description: 'The label size.',
- options: ['medium', 'small'],
- table: {
- category: 'Options',
- defaultValue: { summary: 'small' },
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- },
-} as ComponentMeta;
-
-const Template: ComponentStory = ({
- children,
- ...args
-}) => {children};
-
-/**
- * Label Story
- */
-export const Label = Template.bind({});
-Label.args = {
- children: 'A label',
-};
diff --git a/src/components/atoms/forms/label.test.tsx b/src/components/atoms/forms/label.test.tsx
deleted file mode 100644
index 091737b..0000000
--- a/src/components/atoms/forms/label.test.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { render, screen } from '../../../../tests/utils';
-import { Label } from './label';
-
-describe('Label', () => {
- it('renders a field label', () => {
- render();
- expect(screen.getByText('A label')).toBeDefined();
- });
-});
diff --git a/src/components/atoms/forms/label.tsx b/src/components/atoms/forms/label.tsx
deleted file mode 100644
index 6764579..0000000
--- a/src/components/atoms/forms/label.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { FC, LabelHTMLAttributes, ReactNode } from 'react';
-import styles from './label.module.scss';
-
-export type LabelProps = LabelHTMLAttributes & {
- /**
- * The label body.
- */
- children: ReactNode;
- /**
- * Is the field required? Default: false.
- */
- required?: boolean;
- /**
- * The label size. Default: small.
- */
- size?: 'medium' | 'small';
-};
-
-/**
- * Label Component
- *
- * Render a HTML label element.
- */
-export const Label: FC = ({
- children,
- className = '',
- required = false,
- size = 'small',
- ...props
-}) => {
- const sizeClass = styles[`label--${size}`];
- const labelClass = `${styles.label} ${sizeClass} ${className}`;
-
- return (
-
- );
-};
diff --git a/src/components/atoms/forms/label/index.ts b/src/components/atoms/forms/label/index.ts
new file mode 100644
index 0000000..301fbde
--- /dev/null
+++ b/src/components/atoms/forms/label/index.ts
@@ -0,0 +1 @@
+export * from './label';
diff --git a/src/components/atoms/forms/label/label.module.scss b/src/components/atoms/forms/label/label.module.scss
new file mode 100644
index 0000000..21ba9d3
--- /dev/null
+++ b/src/components/atoms/forms/label/label.module.scss
@@ -0,0 +1,18 @@
+.label {
+ color: var(--color-primary-darker);
+ font-weight: 600;
+ cursor: pointer;
+
+ &--sm {
+ font-size: var(--font-size-sm);
+ font-variant: small-caps;
+ }
+
+ &--md {
+ font-size: var(--font-size-md);
+ }
+}
+
+.required {
+ color: var(--color-secondary);
+}
diff --git a/src/components/atoms/forms/label/label.stories.tsx b/src/components/atoms/forms/label/label.stories.tsx
new file mode 100644
index 0000000..8460c45
--- /dev/null
+++ b/src/components/atoms/forms/label/label.stories.tsx
@@ -0,0 +1,119 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Label as LabelComponent } from './label';
+
+/**
+ * Label - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms',
+ component: LabelComponent,
+ args: {
+ isHidden: false,
+ isRequired: false,
+ size: 'sm',
+ },
+ argTypes: {
+ 'aria-label': {
+ control: {
+ type: 'text',
+ },
+ description: 'Define an accessible name.',
+ table: {
+ category: 'Accessibility',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ className: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add classnames to the label.',
+ table: {
+ category: 'Styles',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ children: {
+ control: {
+ type: 'text',
+ },
+ description: 'The label body.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ htmlFor: {
+ control: {
+ type: 'text',
+ },
+ description: 'The field id.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isHidden: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Set to true if the label should be visually hidden.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ isRequired: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Set to true if the field is required.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ size: {
+ control: {
+ type: 'select',
+ },
+ description: 'The label size.',
+ options: ['md', 'sm'],
+ table: {
+ category: 'Options',
+ defaultValue: { summary: 'sm' },
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta;
+
+const Template: ComponentStory = ({
+ children,
+ ...args
+}) => {children};
+
+/**
+ * Label Story
+ */
+export const Label = Template.bind({});
+Label.args = {
+ children: 'A label',
+};
diff --git a/src/components/atoms/forms/label/label.test.tsx b/src/components/atoms/forms/label/label.test.tsx
new file mode 100644
index 0000000..afdbb94
--- /dev/null
+++ b/src/components/atoms/forms/label/label.test.tsx
@@ -0,0 +1,9 @@
+import { render, screen } from '../../../../../tests/utils';
+import { Label } from './label';
+
+describe('Label', () => {
+ it('renders a field label', () => {
+ render();
+ expect(screen.getByText('A label')).toBeInTheDocument();
+ });
+});
diff --git a/src/components/atoms/forms/label/label.tsx b/src/components/atoms/forms/label/label.tsx
new file mode 100644
index 0000000..5087325
--- /dev/null
+++ b/src/components/atoms/forms/label/label.tsx
@@ -0,0 +1,62 @@
+import { FC, LabelHTMLAttributes, ReactNode } from 'react';
+import styles from './label.module.scss';
+
+export type LabelSize = 'md' | 'sm';
+
+export type LabelProps = Omit<
+ LabelHTMLAttributes,
+ 'hidden' | 'size'
+> & {
+ /**
+ * The label body.
+ */
+ children: ReactNode;
+ /**
+ * Should the label be hidden?
+ *
+ * @default false
+ */
+ isHidden?: boolean;
+ /**
+ * Is the field required?
+ *
+ * @default false
+ */
+ isRequired?: boolean;
+ /**
+ * The label size.
+ *
+ * @default 'sm'
+ */
+ size?: LabelSize;
+};
+
+/**
+ * Label Component
+ *
+ * Render a HTML label element.
+ */
+export const Label: FC = ({
+ children,
+ className = '',
+ isHidden = false,
+ isRequired = false,
+ size = 'sm',
+ ...props
+}) => {
+ const visibilityClass = isHidden ? 'screen-reader-text' : '';
+ const sizeClass = styles[`label--${size}`];
+ const labelClass = `${styles.label} ${sizeClass} ${visibilityClass} ${className}`;
+ const requiredSymbol = ' *';
+
+ return (
+
+ );
+};
diff --git a/src/components/atoms/forms/legend/index.ts b/src/components/atoms/forms/legend/index.ts
new file mode 100644
index 0000000..a0482ef
--- /dev/null
+++ b/src/components/atoms/forms/legend/index.ts
@@ -0,0 +1 @@
+export * from './legend';
diff --git a/src/components/atoms/forms/legend/legend.module.scss b/src/components/atoms/forms/legend/legend.module.scss
new file mode 100644
index 0000000..705e3fe
--- /dev/null
+++ b/src/components/atoms/forms/legend/legend.module.scss
@@ -0,0 +1,6 @@
+.legend {
+ float: left;
+ font-size: var(--font-size-md);
+ font-weight: 600;
+ color: var(--color-primary-darker);
+}
diff --git a/src/components/atoms/forms/legend/legend.stories.tsx b/src/components/atoms/forms/legend/legend.stories.tsx
new file mode 100644
index 0000000..cda7f09
--- /dev/null
+++ b/src/components/atoms/forms/legend/legend.stories.tsx
@@ -0,0 +1,27 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Legend as LegendComponent } from './legend';
+import { Fieldset } from '../fieldset';
+
+/**
+ * Legend - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Forms',
+ component: LegendComponent,
+ args: {},
+ argTypes: {},
+} as ComponentMeta;
+
+const Template: ComponentStory = (args) => (
+
+);
+
+/**
+ * Legend Story
+ */
+export const Legend = Template.bind({});
+Legend.args = {
+ children: 'A fieldset legend',
+};
diff --git a/src/components/atoms/forms/legend/legend.test.tsx b/src/components/atoms/forms/legend/legend.test.tsx
new file mode 100644
index 0000000..7abb996
--- /dev/null
+++ b/src/components/atoms/forms/legend/legend.test.tsx
@@ -0,0 +1,17 @@
+import { render, screen } from '../../../../../tests/utils';
+import { Fieldset } from '../fieldset';
+import { Legend } from './legend';
+
+describe('legend', () => {
+ it('renders the fieldset legend', () => {
+ const body = 'deserunt';
+
+ render(
+
+ );
+
+ expect(screen.getByRole('group')).toHaveTextContent(body);
+ });
+});
diff --git a/src/components/atoms/forms/legend/legend.tsx b/src/components/atoms/forms/legend/legend.tsx
new file mode 100644
index 0000000..b517855
--- /dev/null
+++ b/src/components/atoms/forms/legend/legend.tsx
@@ -0,0 +1,21 @@
+import type { FC, HTMLAttributes } from 'react';
+import styles from './legend.module.scss';
+
+export type LegendProps = HTMLAttributes;
+
+/**
+ * Legend component.
+ */
+export const Legend: FC = ({
+ children,
+ className = '',
+ ...props
+}) => {
+ const legendClass = `${styles.legend} ${className}`;
+
+ return (
+
+ );
+};
diff --git a/src/components/atoms/forms/select.stories.tsx b/src/components/atoms/forms/select.stories.tsx
deleted file mode 100644
index b98ebed..0000000
--- a/src/components/atoms/forms/select.stories.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { useState } from 'react';
-import { Select as SelectComponent } from './select';
-
-const selectOptions = [
- { id: 'option1', name: 'Option 1', value: 'option1' },
- { id: 'option2', name: 'Option 2', value: 'option2' },
- { id: 'option3', name: 'Option 3', value: 'option3' },
-];
-
-/**
- * Select - Storybook Meta
- */
-export default {
- title: 'Atoms/Forms',
- component: SelectComponent,
- args: {
- disabled: false,
- required: false,
- },
- argTypes: {
- 'aria-labelledby': {
- control: {
- type: 'text',
- },
- description: 'One or more ids that refers to the select field name.',
- table: {
- category: 'Accessibility',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Add classnames to the select field.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- disabled: {
- control: {
- type: 'boolean',
- },
- description: 'Field state: either enabled or disabled.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- id: {
- control: {
- type: 'text',
- },
- description: 'Field id.',
- type: {
- name: 'string',
- required: true,
- },
- },
- name: {
- control: {
- type: 'text',
- },
- description: 'Field name.',
- type: {
- name: 'string',
- required: true,
- },
- },
- options: {
- description: 'Select options.',
- type: {
- name: 'array',
- required: true,
- value: {
- name: 'string',
- },
- },
- },
- required: {
- control: {
- type: 'boolean',
- },
- description: 'Determine if the field is required.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- setValue: {
- control: {
- type: null,
- },
- description: 'Callback function to set field value.',
- table: {
- category: 'Events',
- },
- type: {
- name: 'function',
- required: true,
- },
- },
- value: {
- control: {
- type: null,
- },
- description: 'Field value.',
- type: {
- name: 'string',
- required: true,
- },
- },
- },
-} as ComponentMeta;
-
-const Template: ComponentStory = ({
- value,
- setValue: _setValue,
- ...args
-}) => {
- const [selected, setSelected] = useState(value);
-
- return ;
-};
-
-/**
- * Select Story
- */
-export const Select = Template.bind({});
-Select.args = {
- id: 'storybook-select',
- name: 'storybook-select',
- options: selectOptions,
- value: 'option2',
-};
diff --git a/src/components/atoms/forms/select.test.tsx b/src/components/atoms/forms/select.test.tsx
deleted file mode 100644
index 53d9b1f..0000000
--- a/src/components/atoms/forms/select.test.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { render, screen } from '../../../../tests/utils';
-import { Select } from './select';
-
-const selectOptions = [
- { id: 'option1', name: 'Option 1', value: 'option1' },
- { id: 'option2', name: 'Option 2', value: 'option2' },
- { id: 'option3', name: 'Option 3', value: 'option3' },
-];
-const selected = selectOptions[0];
-
-describe('Select', () => {
- it('should correctly set default option', () => {
- render(
-
-
+
>
);
};
diff --git a/src/components/organisms/modals/search-modal.tsx b/src/components/organisms/modals/search-modal.tsx
index 7ba770f..7d772df 100644
--- a/src/components/organisms/modals/search-modal.tsx
+++ b/src/components/organisms/modals/search-modal.tsx
@@ -1,6 +1,6 @@
import { forwardRef, ForwardRefRenderFunction } from 'react';
import { useIntl } from 'react-intl';
-import { Modal, type ModalProps } from '../../molecules';
+import { Heading, Modal, type ModalProps } from '../../atoms';
import { SearchForm, type SearchFormProps } from '../forms';
import styles from './search-modal.module.scss';
@@ -23,8 +23,15 @@ const SearchModalWithRef: ForwardRefRenderFunction<
});
return (
-
-
+
+ {modalTitle}
+
+ }
+ >
+
);
};
diff --git a/src/components/organisms/modals/settings-modal.module.scss b/src/components/organisms/modals/settings-modal.module.scss
index e9b3b85..95626ab 100644
--- a/src/components/organisms/modals/settings-modal.module.scss
+++ b/src/components/organisms/modals/settings-modal.module.scss
@@ -20,27 +20,17 @@
column-gap: var(--spacing-lg);
}
-.items {
+.item {
+ width: 100%;
margin: 0 0 var(--spacing-2xs);
- max-width: unset;
-}
-.fieldset__body {
- margin-left: auto;
+ > *:last-child {
+ margin-left: auto;
+ }
}
-.tooltip {
- font-size: var(--font-size-sm);
- z-index: 2;
+.icon {
+ --icon-size: #{fun.convert-px(30)};
- @media screen and (max-height: #{var.get-breakpoint("2xs")}) {
- width: calc(100vw - var(--spacing-md));
- padding: var(--spacing-md) var(--spacing-2xs) var(--spacing-2xs)
- var(--spacing-2xs);
- right: 0;
- }
-
- @media screen and (min-width: #{var.get-breakpoint("sm")}) {
- width: 100%;
- }
+ margin-right: var(--spacing-2xs);
}
diff --git a/src/components/organisms/modals/settings-modal.stories.tsx b/src/components/organisms/modals/settings-modal.stories.tsx
index 093922d..7af0d60 100644
--- a/src/components/organisms/modals/settings-modal.stories.tsx
+++ b/src/components/organisms/modals/settings-modal.stories.tsx
@@ -1,6 +1,6 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { storageKey as ackeeStorageKey } from '../../molecules/forms/ackee-toggle.fixture';
-import { storageKey as motionStorageKey } from '../../molecules/forms/motion-toggle.fixture';
+import { storageKey as ackeeStorageKey } from '../../organisms/forms/ackee-toggle/ackee-toggle.fixture';
+import { storageKey as motionStorageKey } from '../../organisms/forms/motion-toggle/motion-toggle.fixture';
import { SettingsModal } from './settings-modal';
/**
diff --git a/src/components/organisms/modals/settings-modal.test.tsx b/src/components/organisms/modals/settings-modal.test.tsx
index ec14719..3cd64f6 100644
--- a/src/components/organisms/modals/settings-modal.test.tsx
+++ b/src/components/organisms/modals/settings-modal.test.tsx
@@ -1,6 +1,6 @@
import { render, screen } from '../../../../tests/utils';
-import { storageKey as ackeeStorageKey } from '../../molecules/forms/ackee-toggle.fixture';
-import { storageKey as motionStorageKey } from '../../molecules/forms/motion-toggle.fixture';
+import { storageKey as ackeeStorageKey } from '../../organisms/forms/ackee-toggle/ackee-toggle.fixture';
+import { storageKey as motionStorageKey } from '../../organisms/forms/motion-toggle/motion-toggle.fixture';
import { SettingsModal } from './settings-modal';
describe('SettingsModal', () => {
diff --git a/src/components/organisms/modals/settings-modal.tsx b/src/components/organisms/modals/settings-modal.tsx
index d4a3a49..bb3d886 100644
--- a/src/components/organisms/modals/settings-modal.tsx
+++ b/src/components/organisms/modals/settings-modal.tsx
@@ -1,29 +1,26 @@
import { FC } from 'react';
import { useIntl } from 'react-intl';
-import { Form } from '../../atoms';
+import { Cog, Form, Heading, Modal, type ModalProps } from '../../atoms';
import {
AckeeToggle,
type AckeeToggleProps,
- Modal,
- type ModalProps,
MotionToggle,
type MotionToggleProps,
PrismThemeToggle,
ThemeToggle,
-} from '../../molecules';
+} from '../../organisms';
import styles from './settings-modal.module.scss';
-export type SettingsModalProps = Pick &
- Pick & {
- /**
- * The local storage key for Ackee settings.
- */
- ackeeStorageKey: AckeeToggleProps['storageKey'];
- /**
- * The local storage key for Reduce motion settings.
- */
- motionStorageKey: MotionToggleProps['storageKey'];
- };
+export type SettingsModalProps = Pick & {
+ /**
+ * The local storage key for Ackee settings.
+ */
+ ackeeStorageKey: AckeeToggleProps['storageKey'];
+ /**
+ * The local storage key for Reduce motion settings.
+ */
+ motionStorageKey: MotionToggleProps['storageKey'];
+};
/**
* SettingsModal component
@@ -34,7 +31,6 @@ export const SettingsModal: FC = ({
className = '',
ackeeStorageKey,
motionStorageKey,
- tooltipClassName,
}) => {
const intl = useIntl();
const title = intl.formatMessage({
@@ -51,40 +47,30 @@ export const SettingsModal: FC = ({
return (
+
+ {title}
+
+ }
>
diff --git a/src/components/organisms/toolbar/main-nav.stories.tsx b/src/components/organisms/toolbar/main-nav.stories.tsx
index 1ef10b5..57485d3 100644
--- a/src/components/organisms/toolbar/main-nav.stories.tsx
+++ b/src/components/organisms/toolbar/main-nav.stories.tsx
@@ -57,7 +57,7 @@ export default {
} as ComponentMeta;
const Template: ComponentStory = ({
- isActive,
+ isActive = false,
setIsActive: _setIsActive,
...args
}) => {
diff --git a/src/components/organisms/toolbar/main-nav.tsx b/src/components/organisms/toolbar/main-nav.tsx
index cf49bd4..4182b4c 100644
--- a/src/components/organisms/toolbar/main-nav.tsx
+++ b/src/components/organisms/toolbar/main-nav.tsx
@@ -18,7 +18,7 @@ export type MainNavProps = {
/**
* The button state.
*/
- isActive: BooleanFieldProps['checked'];
+ isActive: BooleanFieldProps['isChecked'];
/**
* The main nav items.
*/
@@ -30,7 +30,7 @@ export type MainNavProps = {
};
const MainNavWithRef: ForwardRefRenderFunction = (
- { className = '', isActive, items, setIsActive },
+ { className = '', isActive = false, items, setIsActive },
ref
) => {
const intl = useIntl();
@@ -49,9 +49,9 @@ const MainNavWithRef: ForwardRefRenderFunction = (
return (
;
const Template: ComponentStory = ({
- isActive,
+ isActive = false,
setIsActive: _setIsActive,
...args
}) => {
diff --git a/src/components/organisms/toolbar/search.tsx b/src/components/organisms/toolbar/search.tsx
index 1b2643c..b20f0d5 100644
--- a/src/components/organisms/toolbar/search.tsx
+++ b/src/components/organisms/toolbar/search.tsx
@@ -19,7 +19,7 @@ export type SearchProps = {
/**
* The button state.
*/
- isActive: BooleanFieldProps['checked'];
+ isActive: BooleanFieldProps['isChecked'];
/**
* A callback function to execute search.
*/
@@ -31,7 +31,7 @@ export type SearchProps = {
};
const SearchWithRef: ForwardRefRenderFunction = (
- { className = '', isActive, searchPage, setIsActive },
+ { className = '', isActive = false, searchPage, setIsActive },
ref
) => {
const intl = useIntl();
@@ -57,9 +57,9 @@ const SearchWithRef: ForwardRefRenderFunction = (
return (
;
const Template: ComponentStory = ({
- isActive,
+ isActive = false,
setIsActive: _setIsActive,
...args
}) => {
diff --git a/src/components/organisms/toolbar/settings.tsx b/src/components/organisms/toolbar/settings.tsx
index 8a4d4a9..3f328a5 100644
--- a/src/components/organisms/toolbar/settings.tsx
+++ b/src/components/organisms/toolbar/settings.tsx
@@ -3,14 +3,13 @@ import { useIntl } from 'react-intl';
import { BooleanField, type BooleanFieldProps, Cog } from '../../atoms';
import { FlippingLabel } from '../../molecules';
import { SettingsModal, type SettingsModalProps } from '../modals';
-import settingsStyles from './settings.module.scss';
-import sharedStyles from './toolbar-items.module.scss';
+import styles from './toolbar-items.module.scss';
export type SettingsProps = SettingsModalProps & {
/**
* The button state.
*/
- isActive: BooleanFieldProps['checked'];
+ isActive: BooleanFieldProps['isChecked'];
/**
* A callback function to handle button state.
*/
@@ -24,10 +23,9 @@ const SettingsWithRef: ForwardRefRenderFunction<
{
ackeeStorageKey,
className = '',
- isActive,
+ isActive = false,
motionStorageKey,
setIsActive,
- tooltipClassName = '',
},
ref
) => {
@@ -45,11 +43,11 @@ const SettingsWithRef: ForwardRefRenderFunction<
});
return (
-
+
@@ -65,9 +63,8 @@ const SettingsWithRef: ForwardRefRenderFunction<
);
diff --git a/src/components/organisms/toolbar/toolbar.tsx b/src/components/organisms/toolbar/toolbar.tsx
index 218b4fb..94c9d95 100644
--- a/src/components/organisms/toolbar/toolbar.tsx
+++ b/src/components/organisms/toolbar/toolbar.tsx
@@ -68,7 +68,6 @@ export const Toolbar: FC
= ({
motionStorageKey={motionStorageKey}
ref={settingsRef}
setIsActive={() => setIsSettingsOpened(!isSettingsOpened)}
- tooltipClassName={styles.tooltip}
/>
);
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx
index 3c6ff17..72bfd3f 100644
--- a/src/components/templates/page/page-layout.tsx
+++ b/src/components/templates/page/page-layout.tsx
@@ -126,11 +126,11 @@ export const PageLayout: FC = ({
const saveComment: CommentFormProps['saveComment'] = async (data, reset) => {
if (!id) throw new Error('Page id missing. Cannot save comment.');
- const { comment: commentBody, email, name, parentId, website } = data;
+ const { author, comment: commentBody, email, parentId, website } = data;
const commentData: SendCommentInput = {
- author: name,
+ author,
authorEmail: email,
- authorUrl: website || '',
+ authorUrl: website ?? '',
clientMutationId: 'contact',
commentOn: id,
content: commentBody,
@@ -248,13 +248,13 @@ export const PageLayout: FC = ({
comments={comments}
depth={2}
Notice={
- isReplyRef.current === true && (
+ isReplyRef.current === true && statusMessage ? (
- )
+ ) : null
}
saveComment={saveComment}
/>
@@ -275,13 +275,13 @@ export const PageLayout: FC = ({
title={commentFormTitle}
titleAlignment="center"
Notice={
- isReplyRef.current === false && (
+ isReplyRef.current === false && statusMessage ? (
- )
+ ) : null
}
/>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index adffe39..02952b4 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -231,10 +231,6 @@
"defaultMessage": "Skip to content",
"description": "Layout: Skip to content link"
},
- "KUowUk": {
- "defaultMessage": "{name} CV",
- "description": "CVPage: CV image alternative text"
- },
"KVSWGP": {
"defaultMessage": "Other thematics",
"description": "ThematicPage: other thematics list widget title"
@@ -463,10 +459,6 @@
"defaultMessage": "Settings",
"description": "SettingsModal: title"
},
- "gX+YVy": {
- "defaultMessage": "Settings form",
- "description": "SettingsForm: an accessible form name"
- },
"hHVgW3": {
"defaultMessage": "Light Theme 🌞",
"description": "usePrism: toggle light theme button text"
@@ -623,6 +615,10 @@
"defaultMessage": "{website} logo",
"description": "Branding: logo title"
},
+ "xYNeKX": {
+ "defaultMessage": "Settings form",
+ "description": "SettingsModal: an accessible form name"
+ },
"xaqaYQ": {
"defaultMessage": "Sending mail...",
"description": "ContactForm: spinner message on submit"
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 3fde110..2ec3657 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -231,10 +231,6 @@
"defaultMessage": "Aller au contenu",
"description": "Layout: Skip to content link"
},
- "KUowUk": {
- "defaultMessage": "CV d’{name}",
- "description": "CVPage: CV image alternative text"
- },
"KVSWGP": {
"defaultMessage": "Autres thématiques",
"description": "ThematicPage: other thematics list widget title"
@@ -463,10 +459,6 @@
"defaultMessage": "Réglages",
"description": "SettingsModal: title"
},
- "gX+YVy": {
- "defaultMessage": "Formulaire des réglages",
- "description": "SettingsForm: an accessible form name"
- },
"hHVgW3": {
"defaultMessage": "Thème clair 🌞",
"description": "usePrism: toggle light theme button text"
@@ -623,6 +615,10 @@
"defaultMessage": "Logo d’{website}",
"description": "Branding: logo title"
},
+ "xYNeKX": {
+ "defaultMessage": "Formulaire des réglages",
+ "description": "SettingsModal: an accessible form name"
+ },
"xaqaYQ": {
"defaultMessage": "Mail en cours d’envoi…",
"description": "ContactForm: spinner message on submit"
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 5dff404..67daae1 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -123,7 +123,7 @@ const Error404Page: NextPageWithLayout = ({
id: 'XKy7rx',
})}
-
+
>
);
diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx
index e3c8a2c..92c58cc 100644
--- a/src/pages/contact.tsx
+++ b/src/pages/contact.tsx
@@ -79,7 +79,7 @@ const ContactPage: NextPageWithLayout = () => {
const [statusMessage, setStatusMessage] = useState('');
const submitMail: ContactFormProps['sendMail'] = async (data, reset) => {
- const { email, message, name, subject } = data;
+ const { email, message, name, object } = data;
const messageHTML = message.replace(/\r?\n/g, '
');
const body = `Message received from ${name} <${email}> on ${website.url}.
${messageHTML}`;
const replyTo = `${name} <${email}>`;
@@ -87,7 +87,7 @@ const ContactPage: NextPageWithLayout = () => {
body,
clientMutationId: 'contact',
replyTo,
- subject,
+ subject: object,
};
const { message: mutationMessage, sent } = await sendMail(mailData);
@@ -139,11 +139,13 @@ const ContactPage: NextPageWithLayout = () => {
+ statusMessage ? (
+
+ ) : undefined
}
/>
--
cgit v1.2.3