From c72cb06f4fbe6e6c3c84bc3b7b8ec7b02f010eae Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 5 Nov 2024 11:33:32 +0100 Subject: [PATCH 01/10] put colors in alphabetical order --- src/styling/variables.scss | 96 +++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/styling/variables.scss b/src/styling/variables.scss index 1db7619..7d1a9e3 100644 --- a/src/styling/variables.scss +++ b/src/styling/variables.scss @@ -16,29 +16,18 @@ // Primitive colors // -$color-neutral-0: #ffffff !default; -$color-neutral-10: #fbfbfb !default; -$color-neutral-20: #f7f7f7 !default; -$color-neutral-30: #eeeeef !default; -$color-neutral-40: #e3e4e5 !default; -$color-neutral-50: #cacacd !default; -$color-neutral-70: #b3b3b7 !default; -$color-neutral-80: #a6a6ab !default; -$color-blueberry-800: #0d4e8a !default; -$color-blueberry-900: #0a3b69 !default; -$color-blueberry-700: #1064b2 !default; -$color-neutral-60: #bdbec1 !default; -$color-blueberry-600: #1580e4 !default; -$color-blueberry-500: #178dfb !default; $color-apple-50: #f1f9e8 !default; $color-apple-100: #d2ebb9 !default; -$color-blueberry-100: #b7dcfe !default; +$color-apple-200: #bde197 !default; +$color-apple-300: #9fd368 !default; +$color-apple-400: #8ccb4a !default; +$color-apple-500: #6fbe1d !default; +$color-apple-600: #65ad1a !default; +$color-apple-700: #4f8715 !default; +$color-apple-800: #3d6910 !default; +$color-apple-900: #2f500c !default; +$color-blackberry-0: #f6f6fb !default; $color-blackberry-10: #e8e7f6 !default; -$color-blueberry-50: #e8f4ff !default; -$color-blueberry-200: #94cbfd !default; -$color-blueberry-300: #64b3fc !default; -$color-blueberry-400: #45a4fc !default; -$color-grape-50: #f0eff9 !default; $color-blackberry-20: #dfdef2 !default; $color-blackberry-30: #c4c2d9 !default; $color-blackberry-40: #8e98a8 !default; @@ -53,34 +42,16 @@ $color-blackberry-700: #202440 !default; $color-blackberry-800: #1a1c37 !default; $color-blackberry-900: #14152e !default; $color-blackberry-1000: #0f0e25 !default; -$color-blackberry-0: #f6f6fb !default; -$color-grape-100: #cfcdec !default; -$color-grape-200: #b8b5e3 !default; -$color-grape-300: #9793d7 !default; -$color-grape-400: #837ecf !default; -$color-grape-500: #645ec3 !default; -$color-grape-600: #5b56b1 !default; -$color-grape-700: #47438a !default; -$color-grape-800: #37346b !default; -$color-grape-900: #2a2752 !default; -$color-neutral-90: #999a9f !default; -$color-neutral-100: #8d8d92 !default; -$color-neutral-200: #808086 !default; -$color-neutral-300: #73747a !default; -$color-neutral-400: #686970 !default; -$color-neutral-500: #5c5d64 !default; -$color-neutral-600: #51525a !default; -$color-neutral-700: #42434c !default; -$color-neutral-800: #363740 !default; -$color-neutral-900: #2b2c36 !default; -$color-apple-200: #bde197 !default; -$color-apple-300: #9fd368 !default; -$color-apple-400: #8ccb4a !default; -$color-apple-500: #6fbe1d !default; -$color-apple-600: #65ad1a !default; -$color-apple-700: #4f8715 !default; -$color-apple-800: #3d6910 !default; -$color-apple-900: #2f500c !default; +$color-blueberry-50: #e8f4ff !default; +$color-blueberry-100: #b7dcfe !default; +$color-blueberry-200: #94cbfd !default; +$color-blueberry-300: #64b3fc !default; +$color-blueberry-400: #45a4fc !default; +$color-blueberry-500: #178dfb !default; +$color-blueberry-600: #1580e4 !default; +$color-blueberry-700: #1064b2 !default; +$color-blueberry-800: #0d4e8a !default; +$color-blueberry-900: #0a3b69 !default; $color-cherry-50: #ffebe7 !default; $color-cherry-100: #ffc0b5 !default; $color-cherry-200: #ffa191 !default; @@ -91,6 +62,16 @@ $color-cherry-600: #e82e0f !default; $color-cherry-700: #b5240b !default; $color-cherry-800: #8c1c09 !default; $color-cherry-900: #6b1507 !default; +$color-grape-50: #f0eff9 !default; +$color-grape-100: #cfcdec !default; +$color-grape-200: #b8b5e3 !default; +$color-grape-300: #9793d7 !default; +$color-grape-400: #837ecf !default; +$color-grape-500: #645ec3 !default; +$color-grape-600: #5b56b1 !default; +$color-grape-700: #47438a !default; +$color-grape-800: #37346b !default; +$color-grape-900: #2a2752 !default; $color-lemon-50: #fbf9e6 !default; $color-lemon-100: #f2ebb1 !default; $color-lemon-200: #ece28b !default; @@ -101,6 +82,25 @@ $color-lemon-600: #c2ae02 !default; $color-lemon-700: #978801 !default; $color-lemon-800: #756901 !default; $color-lemon-900: #595001 !default; +$color-neutral-0: #ffffff !default; +$color-neutral-10: #fbfbfb !default; +$color-neutral-20: #f7f7f7 !default; +$color-neutral-30: #eeeeef !default; +$color-neutral-40: #e3e4e5 !default; +$color-neutral-50: #cacacd !default; +$color-neutral-60: #bdbec1 !default; +$color-neutral-70: #b3b3b7 !default; +$color-neutral-80: #a6a6ab !default; +$color-neutral-90: #999a9f !default; +$color-neutral-100: #8d8d92 !default; +$color-neutral-200: #808086 !default; +$color-neutral-300: #73747a !default; +$color-neutral-400: #686970 !default; +$color-neutral-500: #5c5d64 !default; +$color-neutral-600: #51525a !default; +$color-neutral-700: #42434c !default; +$color-neutral-800: #363740 !default; +$color-neutral-900: #2b2c36 !default; $color-orange-50: #fbf2e6 !default; $color-orange-100: #f2d8b0 !default; $color-orange-200: #ecc58a !default; From 8a8e64ff27958b922fe0db1fa964c0e5fb20c8e4 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 5 Nov 2024 13:15:12 +0100 Subject: [PATCH 02/10] RadioButton component Still missing the proper focus handling --- .../RadioButton/RadioButton.module.scss | 45 ++++++++++++++++++ .../RadioButton/RadioButton.stories.tsx | 47 +++++++++++++++++++ .../controls/RadioButton/RadioButton.tsx | 37 +++++++++++++++ src/styling/variables.scss | 3 ++ 4 files changed, 132 insertions(+) create mode 100644 src/components/forms/controls/RadioButton/RadioButton.module.scss create mode 100644 src/components/forms/controls/RadioButton/RadioButton.stories.tsx create mode 100644 src/components/forms/controls/RadioButton/RadioButton.tsx diff --git a/src/components/forms/controls/RadioButton/RadioButton.module.scss b/src/components/forms/controls/RadioButton/RadioButton.module.scss new file mode 100644 index 0000000..d564e54 --- /dev/null +++ b/src/components/forms/controls/RadioButton/RadioButton.module.scss @@ -0,0 +1,45 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@use '../../../../styling/defs.scss' as bk; + +@layer baklava.components { + .bk-radio-button { + @include bk.component-base(bk-radio-button); + + cursor: pointer; + + appearance: none; + width: 18px; + aspect-ratio: 1; + + border-radius: bk.$border-radius-circle; + background: transparent; + border: 1px solid bk.$theme-radio-default; + + // doing the inner cicle on checked / disabled+checked options without SVG. + background-clip: content-box; + padding: 3px; // distance between background filling and the center of border + // since the border is 2px when checked, it's center is 1px, thus for a distance of 2px we need to have 1 + 2 = 3px. + + &:checked { + background-color: bk.$theme-radio-selected; + border: 2px solid bk.$theme-radio-selected; + } + &:disabled { + border: 1px solid bk.$theme-radio-disabled; + background-color: transparent; + + &:checked { + border: 2px solid bk.$theme-radio-non-active; + background-color: bk.$theme-radio-non-active; + } + } + + // @media (prefers-reduced-motion: no-preference) { + // transition: none 100ms ease-out; + // transition-property: background-color, background-position, border-color; + // } + } +} diff --git a/src/components/forms/controls/RadioButton/RadioButton.stories.tsx b/src/components/forms/controls/RadioButton/RadioButton.stories.tsx new file mode 100644 index 0000000..e2b44d0 --- /dev/null +++ b/src/components/forms/controls/RadioButton/RadioButton.stories.tsx @@ -0,0 +1,47 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import type { Meta, StoryObj } from '@storybook/react'; + +import * as React from 'react'; + +import { RadioButton } from './RadioButton.tsx'; + + +type RadioButtonArgs = React.ComponentProps; +type Story = StoryObj; + +export default { + component: RadioButton, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + }, + args: {}, + decorators: [ + Story =>
{ event.preventDefault(); }}>, + ], + render: (args) => , +} satisfies Meta; + + +export const Checked: Story = { + args: { defaultChecked: true }, +}; + +export const Unchecked: Story = { + args: {}, +}; + +export const DisabledSelected: Story = { + name: 'Disabled (selected)', + args: { disabled: true, defaultChecked: true }, +}; + +export const DisabledUnselected: Story = { + name: 'Disabled (unselected)', + args: { disabled: true }, +}; diff --git a/src/components/forms/controls/RadioButton/RadioButton.tsx b/src/components/forms/controls/RadioButton/RadioButton.tsx new file mode 100644 index 0000000..fe1d020 --- /dev/null +++ b/src/components/forms/controls/RadioButton/RadioButton.tsx @@ -0,0 +1,37 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { classNames as cx, type ComponentProps } from '../../../../util/componentUtil.ts'; +import * as React from 'react'; + +import cl from './RadioButton.module.scss'; + + +export { cl as RadioButtonClassNames }; + +export type RadioButtonProps = ComponentProps<'input'> & { + /** Whether this component should be unstyled. */ + unstyled?: undefined | boolean, +}; +/** + * A simple RadioButton control, just the <input type="radio"> and nothing else.. + */ +export const RadioButton = (props: RadioButtonProps) => { + const { + unstyled = false, + ...propsRest + } = props; + + return ( + + ); +}; diff --git a/src/styling/variables.scss b/src/styling/variables.scss index 7d1a9e3..b3bacc7 100644 --- a/src/styling/variables.scss +++ b/src/styling/variables.scss @@ -281,6 +281,7 @@ $light-button-card-tertiary-text-disabled: $color-neutral-50 !default; $light-radio-default: $color-neutral-300 !default; $light-radio-selected: $color-blueberry-600 !default; $light-radio-disabled: $color-neutral-60 !default; +$light-radio-non-active: $color-blueberry-200 !default; $light-checkbox-background-default: $color-blueberry-600 !default; $light-dropdown-menu-header-background-default: $color-grape-800 !default; $light-dropdown-menu-header-background-hover: $color-blackberry-800 !default; @@ -516,6 +517,7 @@ $dark-button-card-tertiary-text-disabled: $color-neutral-400 !default; $dark-radio-default: $color-neutral-60 !default; $dark-radio-selected: $color-blueberry-500 !default; $dark-radio-disabled: $color-neutral-200 !default; +$dark-radio-non-active: $color-blueberry-900 !default; $dark-checkbox-background-default: $color-blueberry-500 !default; $dark-dropdown-menu-header-background-default: $color-grape-800 !default; $dark-dropdown-menu-header-background-hover: $color-blackberry-800 !default; @@ -751,6 +753,7 @@ $theme-button-card-tertiary-text-disabled: #{ld($light-button-card-tertiary-text $theme-radio-default: #{ld($light-radio-default, $dark-radio-default)} !default; $theme-radio-selected: #{ld($light-radio-selected, $dark-radio-selected)} !default; $theme-radio-disabled: #{ld($light-radio-disabled, $dark-radio-disabled)} !default; +$theme-radio-non-active: #{ld($light-radio-non-active, $dark-radio-non-active)} !default; $theme-checkbox-background-default: #{ld($light-checkbox-background-default, $dark-checkbox-background-default)} !default; $theme-dropdown-menu-header-background-default: #{ld($light-dropdown-menu-header-background-default, $dark-dropdown-menu-header-background-default)} !default; $theme-dropdown-menu-header-background-hover: #{ld($light-dropdown-menu-header-background-hover, $dark-dropdown-menu-header-background-hover)} !default; From 1bedbd234d5fab9858f324cac479607ec3345b8e Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 5 Nov 2024 14:19:19 +0100 Subject: [PATCH 03/10] remove duplicated variables --- src/styling/variables.scss | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/styling/variables.scss b/src/styling/variables.scss index b3bacc7..7d1a9e3 100644 --- a/src/styling/variables.scss +++ b/src/styling/variables.scss @@ -281,7 +281,6 @@ $light-button-card-tertiary-text-disabled: $color-neutral-50 !default; $light-radio-default: $color-neutral-300 !default; $light-radio-selected: $color-blueberry-600 !default; $light-radio-disabled: $color-neutral-60 !default; -$light-radio-non-active: $color-blueberry-200 !default; $light-checkbox-background-default: $color-blueberry-600 !default; $light-dropdown-menu-header-background-default: $color-grape-800 !default; $light-dropdown-menu-header-background-hover: $color-blackberry-800 !default; @@ -517,7 +516,6 @@ $dark-button-card-tertiary-text-disabled: $color-neutral-400 !default; $dark-radio-default: $color-neutral-60 !default; $dark-radio-selected: $color-blueberry-500 !default; $dark-radio-disabled: $color-neutral-200 !default; -$dark-radio-non-active: $color-blueberry-900 !default; $dark-checkbox-background-default: $color-blueberry-500 !default; $dark-dropdown-menu-header-background-default: $color-grape-800 !default; $dark-dropdown-menu-header-background-hover: $color-blackberry-800 !default; @@ -753,7 +751,6 @@ $theme-button-card-tertiary-text-disabled: #{ld($light-button-card-tertiary-text $theme-radio-default: #{ld($light-radio-default, $dark-radio-default)} !default; $theme-radio-selected: #{ld($light-radio-selected, $dark-radio-selected)} !default; $theme-radio-disabled: #{ld($light-radio-disabled, $dark-radio-disabled)} !default; -$theme-radio-non-active: #{ld($light-radio-non-active, $dark-radio-non-active)} !default; $theme-checkbox-background-default: #{ld($light-checkbox-background-default, $dark-checkbox-background-default)} !default; $theme-dropdown-menu-header-background-default: #{ld($light-dropdown-menu-header-background-default, $dark-dropdown-menu-header-background-default)} !default; $theme-dropdown-menu-header-background-hover: #{ld($light-dropdown-menu-header-background-hover, $dark-dropdown-menu-header-background-hover)} !default; From 30952c369567f22ab654803e5c9b56b774ef767d Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 5 Nov 2024 14:23:29 +0100 Subject: [PATCH 04/10] Focused state --- .../RadioButton/RadioButton.module.scss | 4 +++ .../RadioButton/RadioButton.stories.tsx | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/components/forms/controls/RadioButton/RadioButton.module.scss b/src/components/forms/controls/RadioButton/RadioButton.module.scss index d564e54..eea9c7a 100644 --- a/src/components/forms/controls/RadioButton/RadioButton.module.scss +++ b/src/components/forms/controls/RadioButton/RadioButton.module.scss @@ -36,6 +36,10 @@ background-color: bk.$theme-radio-non-active; } } + &:focus-visible, &.pseudo-focused { + outline: 2px solid bk.$theme-radio-focus !important; + outline-offset: 0 !important; + } // @media (prefers-reduced-motion: no-preference) { // transition: none 100ms ease-out; diff --git a/src/components/forms/controls/RadioButton/RadioButton.stories.tsx b/src/components/forms/controls/RadioButton/RadioButton.stories.tsx index e2b44d0..d49186a 100644 --- a/src/components/forms/controls/RadioButton/RadioButton.stories.tsx +++ b/src/components/forms/controls/RadioButton/RadioButton.stories.tsx @@ -8,6 +8,8 @@ import * as React from 'react'; import { RadioButton } from './RadioButton.tsx'; +import cl from './RadioButton.module.scss'; + type RadioButtonArgs = React.ComponentProps; type Story = StoryObj; @@ -38,10 +40,37 @@ export const Unchecked: Story = { export const DisabledSelected: Story = { name: 'Disabled (selected)', - args: { disabled: true, defaultChecked: true }, + args: { + defaultChecked: true, + disabled: true, + }, }; export const DisabledUnselected: Story = { name: 'Disabled (unselected)', args: { disabled: true }, }; + +export const FocusedSelected: Story = { + name: 'Focused (selected)', + args: { + className: cl['pseudo-focused'], + defaultChecked: true, + }, +}; + +export const FocusedUnselected: Story = { + name: 'Focused (unselected)', + args: { + className: cl['pseudo-focused'], + }, +}; + +export const FocusedDisabledSelected: Story = { + name: 'Focused & Disabled (selected)', + args: { + className: cl['pseudo-focused'], + defaultChecked: true, + disabled: true, + }, +}; From c25a4b511eab35072ec43043e2c14b35b3b533fa Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 12 Nov 2024 17:14:00 +0100 Subject: [PATCH 05/10] RadioButtonField component --- .../RadioButtonField.module.scss | 46 +++++++ .../RadioButtonField.stories.tsx | 74 +++++++++++ .../RadioButtonField/RadioButtonField.tsx | 118 ++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss create mode 100644 src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx create mode 100644 src/components/forms/fields/RadioButtonField/RadioButtonField.tsx diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss b/src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss new file mode 100644 index 0000000..daad823 --- /dev/null +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss @@ -0,0 +1,46 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@use '../../../../styling/defs.scss' as bk; + +@layer baklava.components { + .bk-radio-button-field { + @include bk.component-base(bk-radio-button-field); + + .bk-radio-button-field__title { + color: bk.$theme-text-label-default; + font-weight: bk.$font-weight-semibold; + margin-bottom: bk.$spacing-1; + + .bk-radio-button-field__title__icon { + font-size: 18px; + margin-left: bk.$spacing-1; + } + + .bk-radio-button-field__title__optional { + font-size: bk.$font-size-xs; + font-weight: bk.$font-weight-regular; + margin-left: bk.$spacing-1; + } + } + + .bk-radio-button-field__label { + display: flex; + align-items: flex-start; + } + + .bk-radio-button-field__label__content { + color: bk.$theme-text-label-default; + cursor: pointer; + position: relative; + margin-left: bk.$spacing-2; + top: -2px; + } + + .bk-radio-button-field__sublabel { + font-size: bk.$font-size-xs; + padding-left: 26px; + } + } +} diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx new file mode 100644 index 0000000..88a8e08 --- /dev/null +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx @@ -0,0 +1,74 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import type { Meta, StoryObj } from '@storybook/react'; + +import * as React from 'react'; + +import { RadioButtonField } from './RadioButtonField.tsx'; + + +type RadioButtonFieldArgs = React.ComponentProps; +type Story = StoryObj; + +export default { + component: RadioButtonField, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + }, + args: {}, + decorators: [ + Story =>
{ event.preventDefault(); }}>, + ], + render: (args) => , +} satisfies Meta; + + +export const RadioButtonFieldWithLabel: Story = { + args: { + label: 'Label', + }, +}; + +export const RadioButtonFieldWithLabelAndTitle: Story = { + args: { + title: 'Title', + label: 'Label', + }, +}; + +export const RadioButtonFieldWithLabelWithTitleWithTooltip: Story = { + args: { + title: 'Title', + label: 'Label', + titleTooltip: 'This is a tooltip', + } +}; + +export const RadioButtonFieldWithLabelWithTitleWithOptional: Story = { + args: { + title: 'Title', + label: 'Label', + titleOptional: true, + }, +}; + +export const RadioButtonFieldWithLabelWithTitleWithTooltipWithOptional: Story = { + args: { + title: 'Title', + label: 'Label', + titleTooltip: 'This is a tooltip', + titleOptional: true, + }, +}; + +export const RadioButtonFieldWithLabelAndSublabel: Story = { + args: { + label: 'Label', + sublabel: 'Supporting copy', + }, +}; diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx new file mode 100644 index 0000000..0e3dae0 --- /dev/null +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx @@ -0,0 +1,118 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { classNames as cx, type ComponentProps, type ClassNameArgument } from '../../../../util/componentUtil.ts'; +import * as React from 'react'; + +import { RadioButton } from '../../controls/RadioButton/RadioButton.tsx'; +import { Icon } from '../../../graphics/Icon/Icon.tsx'; +import { TooltipProvider } from '../../../overlays/Tooltip/TooltipProvider.tsx'; + +import cl from './RadioButtonField.module.scss'; + + +export { cl as RadioButtonFieldClassNames }; + +export type RadioButtonFieldTitleProps = React.PropsWithChildren<{ + className?: ClassNameArgument; + + /** Whether to display the optional observation on title. */ + titleOptional?: undefined | boolean, + + /** An optional tooltip to be displayed on an info icon next to the title. */ + titleTooltip?: undefined | string, +}>; + +export const RadioButtonFieldTitle = ({ className, children, titleOptional, titleTooltip }: RadioButtonFieldTitleProps) => ( +

+ {children} + {titleTooltip && ( + + + + )} + {titleOptional && ( + (Optional) + )} +

+); + +export type RadioButtonFieldProps = ComponentProps<'div'> & { + /** Whether this component should be unstyled. */ + unstyled?: undefined | boolean, + + /** A label to be displayed after the radio button. */ + label: string, + + /** An optional supporting copy to be displayed under the label. */ + sublabel?: undefined | string, + + /** An optional title. */ + title?: undefined | string, + + /** An optional tooltip to be displayed on an info icon next to the title. */ + titleTooltip?: undefined | string, + + /** Whether to display the optional observation on title. */ + titleOptional?: undefined | boolean, + + /** Whether the radio button is selected by default. Passed down to Radio Button component. */ + defaultChecked?: undefined | boolean, + + /** Whether the radio button is selected. Passed down to Radio Button component. */ + checked?: undefined | boolean, + + /** Whether the radio button is disabled. Passed down to Radio Button component. */ + disabled?: undefined | boolean, +}; + +/** + * A full-fledged Radio Button field, with optional label, title, icon etc. + */ +export const RadioButtonField = (props: RadioButtonFieldProps) => { + const { + unstyled = false, + label = '', + sublabel, + title, + titleOptional, + titleTooltip, + className, + } = props; + + return ( +
+ {title && ( + + {title} + + )} + {/* biome ignore lint/a11y/noLabelWithoutControl: the `` will resolve to an `` */} + + {sublabel && ( +
{sublabel}
+ )} +
+ ); +}; From c98e23221d972d82993c9c7d03e645cf1038ca44 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 12 Nov 2024 17:24:51 +0100 Subject: [PATCH 06/10] RadioButtonGroup as a simple copy of CheckboxGroup --- .../RadioButtonGroup.module.scss | 22 +++++++++++ .../RadioButtonGroup.stories.tsx | 36 ++++++++++++++++++ .../RadioButtonGroup/RadioButtonGroup.tsx | 37 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss create mode 100644 src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx create mode 100644 src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss new file mode 100644 index 0000000..67717b4 --- /dev/null +++ b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss @@ -0,0 +1,22 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@use '../../../../styling/defs.scss' as bk; + +@layer baklava.components { + .bk-radio-button-group { + @include bk.component-base(bk-radio-button-group); + display: flex; + + &.bk-radio-button-group--vertical { + flex-direction: column; + row-gap: 20px; + } + + &.bk-radio-button-group--horizontal { + flex-direction: row; + column-gap: bk.$spacing-7; + } + } +} diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx new file mode 100644 index 0000000..6956e46 --- /dev/null +++ b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx @@ -0,0 +1,36 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import * as React from 'react'; + +import type { Meta, StoryObj } from '@storybook/react'; + +import { RadioButtonGroup } from './RadioButtonGroup.tsx'; + + +type RadioButtonGroupArgs = React.ComponentProps; +type Story = StoryObj; + +export default { + component: RadioButtonGroup, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + }, + render: (args) => + + + + , +} satisfies Meta; + +export const RadioButtonGroupVertical: Story = {}; + +export const RadioButtonGroupHorizontal: Story = { + args: { + direction: 'horizontal', + }, +}; diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx new file mode 100644 index 0000000..daf0f75 --- /dev/null +++ b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx @@ -0,0 +1,37 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { classNames as cx } from '../../../../util/componentUtil.ts'; +import * as React from 'react'; + +import cl from './RadioButtonGroup.module.scss'; + +import { RadioButtonField } from '../RadioButtonField/RadioButtonField.tsx'; + + +export { cl as RadioButtonGroupClassNames }; + +export type RadioButtonGroupProps = React.PropsWithChildren<{ + direction?: undefined | "vertical" | "horizontal"; +}>; + +/** + * Radio button group component, wrapping multiple RadioButtonField components vertically or horizontally. + */ +export const RadioButtonGroup = Object.assign( + (props: RadioButtonGroupProps) => { + const { children, direction = 'vertical' } = props; + return ( +
+ {children} +
+ ); + }, + { RadioButtonField }, +); From 7901f779be88d34520aa2d3c1d547467773a871f Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Tue, 12 Nov 2024 18:11:48 +0100 Subject: [PATCH 07/10] RadioButtonGroup with stateful example --- .../RadioButtonField/RadioButtonField.tsx | 1 + .../RadioButtonGroup.stories.tsx | 25 +++++++++++++++---- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx index 0e3dae0..edd96d8 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx @@ -105,6 +105,7 @@ export const RadioButtonField = (props: RadioButtonFieldProps) => { checked={props.checked} defaultChecked={props.defaultChecked} disabled={props.disabled} + onChange={props.onChange} /> {label} diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx index 6956e46..e8a1a78 100644 --- a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx +++ b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx @@ -12,6 +12,9 @@ import { RadioButtonGroup } from './RadioButtonGroup.tsx'; type RadioButtonGroupArgs = React.ComponentProps; type Story = StoryObj; +const Color = ['Red', 'Green', 'Blue'] as const; +type Color = (typeof Color)[number]; + export default { component: RadioButtonGroup, parameters: { @@ -20,11 +23,23 @@ export default { tags: ['autodocs'], argTypes: { }, - render: (args) => - - - - , + render: (args) => { + const [selectedColor, setSelectedColor] = React.useState(Color[0]); + // TODO: how to make a typed element? such as {...args}> + return ( + + {Color.map(color => + setSelectedColor(color)} + /> + )} + + ); + }, } satisfies Meta; export const RadioButtonGroupVertical: Story = {}; From 7af58b6a2b26a1e83035f31485d0ecdf9ded5fd4 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Wed, 13 Nov 2024 17:53:19 +0100 Subject: [PATCH 08/10] missing prop --- .../forms/fields/RadioButtonField/RadioButtonField.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx index edd96d8..c008b6d 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx @@ -69,6 +69,9 @@ export type RadioButtonFieldProps = ComponentProps<'div'> & { /** Whether the radio button is disabled. Passed down to Radio Button component. */ disabled?: undefined | boolean, + + /** The onChange event for the radio button. Passed down to Radio Button component. */ + onChange?: (e: React.FormEvent) => void, }; /** From fb855b8b880cddcbcbc98e579d7762d1ddcf0eac Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Thu, 14 Nov 2024 11:00:31 +0100 Subject: [PATCH 09/10] Code Review: change titleOptional to optional and lint types --- .../RadioButtonField/RadioButtonField.stories.tsx | 4 ++-- .../fields/RadioButtonField/RadioButtonField.tsx | 14 +++++++------- .../fields/RadioButtonGroup/RadioButtonGroup.tsx | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx index 88a8e08..89082d8 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx @@ -53,7 +53,7 @@ export const RadioButtonFieldWithLabelWithTitleWithOptional: Story = { args: { title: 'Title', label: 'Label', - titleOptional: true, + optional: true, }, }; @@ -62,7 +62,7 @@ export const RadioButtonFieldWithLabelWithTitleWithTooltipWithOptional: Story = title: 'Title', label: 'Label', titleTooltip: 'This is a tooltip', - titleOptional: true, + optional: true, }, }; diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx index c008b6d..07e7a4b 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx +++ b/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx @@ -15,16 +15,16 @@ import cl from './RadioButtonField.module.scss'; export { cl as RadioButtonFieldClassNames }; export type RadioButtonFieldTitleProps = React.PropsWithChildren<{ - className?: ClassNameArgument; + className?: ClassNameArgument, /** Whether to display the optional observation on title. */ - titleOptional?: undefined | boolean, + optional?: undefined | boolean, /** An optional tooltip to be displayed on an info icon next to the title. */ titleTooltip?: undefined | string, }>; -export const RadioButtonFieldTitle = ({ className, children, titleOptional, titleTooltip }: RadioButtonFieldTitleProps) => ( +export const RadioButtonFieldTitle = ({ className, children, optional, titleTooltip }: RadioButtonFieldTitleProps) => (

)} - {titleOptional && ( + {optional && ( (Optional) )}

@@ -59,7 +59,7 @@ export type RadioButtonFieldProps = ComponentProps<'div'> & { titleTooltip?: undefined | string, /** Whether to display the optional observation on title. */ - titleOptional?: undefined | boolean, + optional?: undefined | boolean, /** Whether the radio button is selected by default. Passed down to Radio Button component. */ defaultChecked?: undefined | boolean, @@ -83,7 +83,7 @@ export const RadioButtonField = (props: RadioButtonFieldProps) => { label = '', sublabel, title, - titleOptional, + optional, titleTooltip, className, } = props; @@ -96,7 +96,7 @@ export const RadioButtonField = (props: RadioButtonFieldProps) => { )}> {title && ( {title} diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx index daf0f75..cda3ea9 100644 --- a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx +++ b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx @@ -13,7 +13,7 @@ import { RadioButtonField } from '../RadioButtonField/RadioButtonField.tsx'; export { cl as RadioButtonGroupClassNames }; export type RadioButtonGroupProps = React.PropsWithChildren<{ - direction?: undefined | "vertical" | "horizontal"; + direction?: undefined | "vertical" | "horizontal", }>; /** From 1cf0f187a41638d62690035b19109f426034a354 Mon Sep 17 00:00:00 2001 From: Arlindo Pereira Date: Thu, 14 Nov 2024 13:36:10 +0100 Subject: [PATCH 10/10] Code Review: Refactor RadioButton* to Radio* --- .../Radio.module.scss} | 13 +++--- .../Radio.stories.tsx} | 14 +++--- .../RadioButton.tsx => Radio/Radio.tsx} | 12 ++--- .../RadioButtonGroup/RadioButtonGroup.tsx | 37 ---------------- .../RadioField.module.scss} | 18 ++++---- .../RadioField.stories.tsx} | 24 +++++----- .../RadioField.tsx} | 44 +++++++++---------- .../RadioGroup.module.scss} | 8 ++-- .../RadioGroup.stories.tsx} | 22 +++++----- .../forms/fields/RadioGroup/RadioGroup.tsx | 37 ++++++++++++++++ 10 files changed, 115 insertions(+), 114 deletions(-) rename src/components/forms/controls/{RadioButton/RadioButton.module.scss => Radio/Radio.module.scss} (81%) rename src/components/forms/controls/{RadioButton/RadioButton.stories.tsx => Radio/Radio.stories.tsx} (81%) rename src/components/forms/controls/{RadioButton/RadioButton.tsx => Radio/Radio.tsx} (66%) delete mode 100644 src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx rename src/components/forms/fields/{RadioButtonField/RadioButtonField.module.scss => RadioField/RadioField.module.scss} (70%) rename src/components/forms/fields/{RadioButtonField/RadioButtonField.stories.tsx => RadioField/RadioField.stories.tsx} (59%) rename src/components/forms/fields/{RadioButtonField/RadioButtonField.tsx => RadioField/RadioField.tsx} (61%) rename src/components/forms/fields/{RadioButtonGroup/RadioButtonGroup.module.scss => RadioGroup/RadioGroup.module.scss} (73%) rename src/components/forms/fields/{RadioButtonGroup/RadioButtonGroup.stories.tsx => RadioGroup/RadioGroup.stories.tsx} (62%) create mode 100644 src/components/forms/fields/RadioGroup/RadioGroup.tsx diff --git a/src/components/forms/controls/RadioButton/RadioButton.module.scss b/src/components/forms/controls/Radio/Radio.module.scss similarity index 81% rename from src/components/forms/controls/RadioButton/RadioButton.module.scss rename to src/components/forms/controls/Radio/Radio.module.scss index eea9c7a..8aee3d1 100644 --- a/src/components/forms/controls/RadioButton/RadioButton.module.scss +++ b/src/components/forms/controls/Radio/Radio.module.scss @@ -5,8 +5,8 @@ @use '../../../../styling/defs.scss' as bk; @layer baklava.components { - .bk-radio-button { - @include bk.component-base(bk-radio-button); + .bk-radio { + @include bk.component-base(bk-radio); cursor: pointer; @@ -37,13 +37,14 @@ } } &:focus-visible, &.pseudo-focused { + // those have !important to override CSS rules on layer accessibility outline: 2px solid bk.$theme-radio-focus !important; outline-offset: 0 !important; } - // @media (prefers-reduced-motion: no-preference) { - // transition: none 100ms ease-out; - // transition-property: background-color, background-position, border-color; - // } + @media (prefers-reduced-motion: no-preference) { + transition: none 100ms ease-out; + transition-property: background-color, background-position, border-color; + } } } diff --git a/src/components/forms/controls/RadioButton/RadioButton.stories.tsx b/src/components/forms/controls/Radio/Radio.stories.tsx similarity index 81% rename from src/components/forms/controls/RadioButton/RadioButton.stories.tsx rename to src/components/forms/controls/Radio/Radio.stories.tsx index d49186a..5f4c0c0 100644 --- a/src/components/forms/controls/RadioButton/RadioButton.stories.tsx +++ b/src/components/forms/controls/Radio/Radio.stories.tsx @@ -6,16 +6,16 @@ import type { Meta, StoryObj } from '@storybook/react'; import * as React from 'react'; -import { RadioButton } from './RadioButton.tsx'; +import { Radio } from './Radio.tsx'; -import cl from './RadioButton.module.scss'; +import cl from './Radio.module.scss'; -type RadioButtonArgs = React.ComponentProps; -type Story = StoryObj; +type RadioArgs = React.ComponentProps; +type Story = StoryObj; export default { - component: RadioButton, + component: Radio, parameters: { layout: 'centered', }, @@ -26,8 +26,8 @@ export default { decorators: [ Story =>
{ event.preventDefault(); }}>, ], - render: (args) => , -} satisfies Meta; + render: (args) => , +} satisfies Meta; export const Checked: Story = { diff --git a/src/components/forms/controls/RadioButton/RadioButton.tsx b/src/components/forms/controls/Radio/Radio.tsx similarity index 66% rename from src/components/forms/controls/RadioButton/RadioButton.tsx rename to src/components/forms/controls/Radio/Radio.tsx index fe1d020..d086d83 100644 --- a/src/components/forms/controls/RadioButton/RadioButton.tsx +++ b/src/components/forms/controls/Radio/Radio.tsx @@ -5,19 +5,19 @@ import { classNames as cx, type ComponentProps } from '../../../../util/componentUtil.ts'; import * as React from 'react'; -import cl from './RadioButton.module.scss'; +import cl from './Radio.module.scss'; -export { cl as RadioButtonClassNames }; +export { cl as RadioClassNames }; -export type RadioButtonProps = ComponentProps<'input'> & { +export type RadioProps = ComponentProps<'input'> & { /** Whether this component should be unstyled. */ unstyled?: undefined | boolean, }; /** - * A simple RadioButton control, just the <input type="radio"> and nothing else.. + * A simple Radio control, just the <input type="radio"> and nothing else.. */ -export const RadioButton = (props: RadioButtonProps) => { +export const Radio = (props: RadioProps) => { const { unstyled = false, ...propsRest @@ -29,7 +29,7 @@ export const RadioButton = (props: RadioButtonProps) => { {...propsRest} className={cx( 'bk', - { [cl['bk-radio-button']]: !unstyled }, + { [cl['bk-radio']]: !unstyled }, propsRest.className, )} /> diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx b/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx deleted file mode 100644 index cda3ea9..0000000 --- a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* Copyright (c) Fortanix, Inc. -|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of -|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { classNames as cx } from '../../../../util/componentUtil.ts'; -import * as React from 'react'; - -import cl from './RadioButtonGroup.module.scss'; - -import { RadioButtonField } from '../RadioButtonField/RadioButtonField.tsx'; - - -export { cl as RadioButtonGroupClassNames }; - -export type RadioButtonGroupProps = React.PropsWithChildren<{ - direction?: undefined | "vertical" | "horizontal", -}>; - -/** - * Radio button group component, wrapping multiple RadioButtonField components vertically or horizontally. - */ -export const RadioButtonGroup = Object.assign( - (props: RadioButtonGroupProps) => { - const { children, direction = 'vertical' } = props; - return ( -
- {children} -
- ); - }, - { RadioButtonField }, -); diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss b/src/components/forms/fields/RadioField/RadioField.module.scss similarity index 70% rename from src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss rename to src/components/forms/fields/RadioField/RadioField.module.scss index daad823..3f175c2 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.module.scss +++ b/src/components/forms/fields/RadioField/RadioField.module.scss @@ -5,40 +5,40 @@ @use '../../../../styling/defs.scss' as bk; @layer baklava.components { - .bk-radio-button-field { - @include bk.component-base(bk-radio-button-field); + .bk-radio-field { + @include bk.component-base(bk-radio-field); - .bk-radio-button-field__title { + .bk-radio-field__title { color: bk.$theme-text-label-default; font-weight: bk.$font-weight-semibold; margin-bottom: bk.$spacing-1; - .bk-radio-button-field__title__icon { + .bk-radio-field__title__icon { font-size: 18px; margin-left: bk.$spacing-1; } - .bk-radio-button-field__title__optional { + .bk-radio-field__title__optional { font-size: bk.$font-size-xs; font-weight: bk.$font-weight-regular; margin-left: bk.$spacing-1; } } - .bk-radio-button-field__label { + .bk-radio-field__label { display: flex; align-items: flex-start; } - .bk-radio-button-field__label__content { + .bk-radio-field__label__content { color: bk.$theme-text-label-default; cursor: pointer; position: relative; - margin-left: bk.$spacing-2; + padding-left: bk.$spacing-2; top: -2px; } - .bk-radio-button-field__sublabel { + .bk-radio-field__sublabel { font-size: bk.$font-size-xs; padding-left: 26px; } diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx b/src/components/forms/fields/RadioField/RadioField.stories.tsx similarity index 59% rename from src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx rename to src/components/forms/fields/RadioField/RadioField.stories.tsx index 89082d8..a71cc06 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.stories.tsx +++ b/src/components/forms/fields/RadioField/RadioField.stories.tsx @@ -6,14 +6,14 @@ import type { Meta, StoryObj } from '@storybook/react'; import * as React from 'react'; -import { RadioButtonField } from './RadioButtonField.tsx'; +import { RadioField } from './RadioField.tsx'; -type RadioButtonFieldArgs = React.ComponentProps; -type Story = StoryObj; +type RadioFieldArgs = React.ComponentProps; +type Story = StoryObj; export default { - component: RadioButtonField, + component: RadioField, parameters: { layout: 'centered', }, @@ -24,24 +24,24 @@ export default { decorators: [ Story =>
{ event.preventDefault(); }}>, ], - render: (args) => , -} satisfies Meta; + render: (args) => , +} satisfies Meta; -export const RadioButtonFieldWithLabel: Story = { +export const RadioFieldWithLabel: Story = { args: { label: 'Label', }, }; -export const RadioButtonFieldWithLabelAndTitle: Story = { +export const RadioFieldWithLabelAndTitle: Story = { args: { title: 'Title', label: 'Label', }, }; -export const RadioButtonFieldWithLabelWithTitleWithTooltip: Story = { +export const RadioFieldWithLabelWithTitleWithTooltip: Story = { args: { title: 'Title', label: 'Label', @@ -49,7 +49,7 @@ export const RadioButtonFieldWithLabelWithTitleWithTooltip: Story = { } }; -export const RadioButtonFieldWithLabelWithTitleWithOptional: Story = { +export const RadioFieldWithLabelWithTitleWithOptional: Story = { args: { title: 'Title', label: 'Label', @@ -57,7 +57,7 @@ export const RadioButtonFieldWithLabelWithTitleWithOptional: Story = { }, }; -export const RadioButtonFieldWithLabelWithTitleWithTooltipWithOptional: Story = { +export const RadioFieldWithLabelWithTitleWithTooltipWithOptional: Story = { args: { title: 'Title', label: 'Label', @@ -66,7 +66,7 @@ export const RadioButtonFieldWithLabelWithTitleWithTooltipWithOptional: Story = }, }; -export const RadioButtonFieldWithLabelAndSublabel: Story = { +export const RadioFieldWithLabelAndSublabel: Story = { args: { label: 'Label', sublabel: 'Supporting copy', diff --git a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx b/src/components/forms/fields/RadioField/RadioField.tsx similarity index 61% rename from src/components/forms/fields/RadioButtonField/RadioButtonField.tsx rename to src/components/forms/fields/RadioField/RadioField.tsx index 07e7a4b..730bdcf 100644 --- a/src/components/forms/fields/RadioButtonField/RadioButtonField.tsx +++ b/src/components/forms/fields/RadioField/RadioField.tsx @@ -5,16 +5,16 @@ import { classNames as cx, type ComponentProps, type ClassNameArgument } from '../../../../util/componentUtil.ts'; import * as React from 'react'; -import { RadioButton } from '../../controls/RadioButton/RadioButton.tsx'; +import { Radio } from '../../controls/Radio/Radio.tsx'; import { Icon } from '../../../graphics/Icon/Icon.tsx'; import { TooltipProvider } from '../../../overlays/Tooltip/TooltipProvider.tsx'; -import cl from './RadioButtonField.module.scss'; +import cl from './RadioField.module.scss'; -export { cl as RadioButtonFieldClassNames }; +export { cl as RadioFieldClassNames }; -export type RadioButtonFieldTitleProps = React.PropsWithChildren<{ +export type RadioTitleProps = React.PropsWithChildren<{ className?: ClassNameArgument, /** Whether to display the optional observation on title. */ @@ -24,25 +24,25 @@ export type RadioButtonFieldTitleProps = React.PropsWithChildren<{ titleTooltip?: undefined | string, }>; -export const RadioButtonFieldTitle = ({ className, children, optional, titleTooltip }: RadioButtonFieldTitleProps) => ( +export const RadioFieldTitle = ({ className, children, optional, titleTooltip }: RadioTitleProps) => (

{children} {titleTooltip && ( - + )} {optional && ( - (Optional) + (Optional) )}

); -export type RadioButtonFieldProps = ComponentProps<'div'> & { +export type RadioFieldProps = ComponentProps<'div'> & { /** Whether this component should be unstyled. */ unstyled?: undefined | boolean, @@ -61,23 +61,23 @@ export type RadioButtonFieldProps = ComponentProps<'div'> & { /** Whether to display the optional observation on title. */ optional?: undefined | boolean, - /** Whether the radio button is selected by default. Passed down to Radio Button component. */ + /** Whether the radio is selected by default. Passed down to Radio component. */ defaultChecked?: undefined | boolean, - /** Whether the radio button is selected. Passed down to Radio Button component. */ + /** Whether the radio is selected. Passed down to Radio component. */ checked?: undefined | boolean, - /** Whether the radio button is disabled. Passed down to Radio Button component. */ + /** Whether the radio is disabled. Passed down to Radio component. */ disabled?: undefined | boolean, - /** The onChange event for the radio button. Passed down to Radio Button component. */ + /** The onChange event for the radio. Passed down to Radio component. */ onChange?: (e: React.FormEvent) => void, }; /** - * A full-fledged Radio Button field, with optional label, title, icon etc. + * A full-fledged Radio field, with optional label, title, icon etc. */ -export const RadioButtonField = (props: RadioButtonFieldProps) => { +export const RadioField = (props: RadioFieldProps) => { const { unstyled = false, label = '', @@ -91,31 +91,31 @@ export const RadioButtonField = (props: RadioButtonFieldProps) => { return (
{title && ( - {title} - + )} {/* biome ignore lint/a11y/noLabelWithoutControl: the `` will resolve to an `` */} - {sublabel && ( -
{sublabel}
+
{sublabel}
)}
); diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss b/src/components/forms/fields/RadioGroup/RadioGroup.module.scss similarity index 73% rename from src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss rename to src/components/forms/fields/RadioGroup/RadioGroup.module.scss index 67717b4..d84fc67 100644 --- a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.module.scss +++ b/src/components/forms/fields/RadioGroup/RadioGroup.module.scss @@ -5,16 +5,16 @@ @use '../../../../styling/defs.scss' as bk; @layer baklava.components { - .bk-radio-button-group { - @include bk.component-base(bk-radio-button-group); + .bk-radio-group { + @include bk.component-base(bk-radio-group); display: flex; - &.bk-radio-button-group--vertical { + &.bk-radio-group--vertical { flex-direction: column; row-gap: 20px; } - &.bk-radio-button-group--horizontal { + &.bk-radio-group--horizontal { flex-direction: row; column-gap: bk.$spacing-7; } diff --git a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx b/src/components/forms/fields/RadioGroup/RadioGroup.stories.tsx similarity index 62% rename from src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx rename to src/components/forms/fields/RadioGroup/RadioGroup.stories.tsx index e8a1a78..f2d3309 100644 --- a/src/components/forms/fields/RadioButtonGroup/RadioButtonGroup.stories.tsx +++ b/src/components/forms/fields/RadioGroup/RadioGroup.stories.tsx @@ -6,17 +6,17 @@ import * as React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; -import { RadioButtonGroup } from './RadioButtonGroup.tsx'; +import { RadioGroup } from './RadioGroup.tsx'; -type RadioButtonGroupArgs = React.ComponentProps; -type Story = StoryObj; +type RadioGroupArgs = React.ComponentProps; +type Story = StoryObj; const Color = ['Red', 'Green', 'Blue'] as const; type Color = (typeof Color)[number]; export default { - component: RadioButtonGroup, + component: RadioGroup, parameters: { layout: 'centered', }, @@ -25,26 +25,26 @@ export default { }, render: (args) => { const [selectedColor, setSelectedColor] = React.useState(Color[0]); - // TODO: how to make a typed element? such as {...args}> + // TODO: how to make a typed element? such as {...args}> return ( - {Color.map(color => - setSelectedColor(color)} /> )} - + ); }, -} satisfies Meta; +} satisfies Meta; -export const RadioButtonGroupVertical: Story = {}; +export const RadioGroupVertical: Story = {}; -export const RadioButtonGroupHorizontal: Story = { +export const RadioGroupHorizontal: Story = { args: { direction: 'horizontal', }, diff --git a/src/components/forms/fields/RadioGroup/RadioGroup.tsx b/src/components/forms/fields/RadioGroup/RadioGroup.tsx new file mode 100644 index 0000000..668952b --- /dev/null +++ b/src/components/forms/fields/RadioGroup/RadioGroup.tsx @@ -0,0 +1,37 @@ +/* Copyright (c) Fortanix, Inc. +|* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of +|* the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { classNames as cx } from '../../../../util/componentUtil.ts'; +import * as React from 'react'; + +import cl from './RadioGroup.module.scss'; + +import { RadioField } from '../RadioField/RadioField.tsx'; + + +export { cl as RadioGroupClassNames }; + +export type RadioGroupProps = React.PropsWithChildren<{ + direction?: undefined | "vertical" | "horizontal", +}>; + +/** + * Radio group component, wrapping multiple RadioField components vertically or horizontally. + */ +export const RadioGroup = Object.assign( + (props: RadioGroupProps) => { + const { children, direction = 'vertical' } = props; + return ( +
+ {children} +
+ ); + }, + { RadioField }, +);