diff --git a/src/components/experimental/RadioButton/RadioButton.tsx b/src/components/experimental/RadioButton/RadioButton.tsx new file mode 100644 index 00000000..a7155996 --- /dev/null +++ b/src/components/experimental/RadioButton/RadioButton.tsx @@ -0,0 +1,93 @@ +import React, { FC } from 'react'; +import styled from 'styled-components'; +import { Radio as BaseRadio, RadioProps } from 'react-aria-components'; + +import { getSemanticValue } from '../../../essentials/experimental'; +import { themeGet } from '../../../utils/experimental'; + +const Indicator = styled.span<{ $selected: boolean }>` + position: relative; + flex-shrink: 0; + + top: calc((var(--wave-exp-typescale-body-1-line-height) - 1rem) / 2); + width: 1rem; + height: 1rem; + + box-sizing: border-box; + border-style: solid; + border-color: currentColor; + border-width: ${props => (props.$selected ? '5px' : '2px')}; + border-radius: 50%; + background-color: ${getSemanticValue('surface')}; + transition: border-color 200ms ease, border-width 50ms ease; +`; + +const Radio = styled(BaseRadio)` + display: flex; + gap: ${themeGet('space.2')}; + cursor: pointer; + + font-family: var(--wave-exp-typescale-body-1-font), sans-serif; + font-size: var(--wave-exp-typescale-body-1-size); + font-weight: var(--wave-exp-typescale-body-1-weight); + line-height: var(--wave-exp-typescale-body-1-line-height); + + color: ${getSemanticValue('on-surface')}; + + ${Indicator} { + color: ${getSemanticValue('divider')}; + } + + &[data-hovered] ${Indicator} { + color: ${getSemanticValue('interactive')}; + } + + &[data-pressed] ${Indicator} { + color: ${getSemanticValue('surface-variant')}; + } + + &[data-focus-visible] { + outline: 2px solid ${getSemanticValue('surface-variant')}; + outline-offset: 2px; + } + + &[data-disabled] { + cursor: not-allowed; + opacity: 0.38; + } + + &[data-invalid] { + color: ${getSemanticValue('negative-variant')}; + } + + &[data-hovered][data-invalid] ${Indicator} { + color: ${getSemanticValue('negative')}; + } + + &[data-selected] ${Indicator} { + color: ${getSemanticValue('accent')}; + } + + &[data-selected][data-hovered] ${Indicator} { + color: ${getSemanticValue('on-interactive-container')}; + } + + &[data-selected][data-pressed] ${Indicator} { + color: ${getSemanticValue('interactive')}; + } + + &[data-selected][data-disabled] ${Indicator} { + color: ${getSemanticValue('surface-variant')}; + } +`; + +export const RadioButton: FC = ({ children, ...rest }) => ( + + {({ isSelected }) => ( + <> + + {children} + + )} + +); diff --git a/src/components/experimental/RadioButton/docs/RadioButton.stories.tsx b/src/components/experimental/RadioButton/docs/RadioButton.stories.tsx new file mode 100644 index 00000000..479f3e40 --- /dev/null +++ b/src/components/experimental/RadioButton/docs/RadioButton.stories.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import { RadioGroup } from 'react-aria-components'; +import { RadioButton } from '../RadioButton'; + +const meta: Meta = { + title: 'Experimental/Components/RadioButton', + component: RadioButton, + args: { + children: 'Label' + }, + decorators: [ + story => ( + + {story} + + ) + ] +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Disabled: Story = { + args: { + isDisabled: true + } +}; + +export const Selected: Story = { + args: { + value: 'test' + } +}; diff --git a/src/components/experimental/RadioGroup/RadioGroup.styled.ts b/src/components/experimental/RadioGroup/RadioGroup.styled.ts new file mode 100644 index 00000000..f6c49d1c --- /dev/null +++ b/src/components/experimental/RadioGroup/RadioGroup.styled.ts @@ -0,0 +1,19 @@ +import { RadioGroup as BaseRadioGroup } from 'react-aria-components'; +import styled from 'styled-components'; + +import { themeGet } from '../../../utils/experimental'; + +export const List = styled.div` + display: flex; + gap: ${themeGet('space.4')}; +`; + +export const RadioGroup = styled(BaseRadioGroup)` + &[data-orientation='horizontal'] ${List} { + flex-direction: row; + } + + &[data-orientation='vertical'] ${List} { + flex-direction: column; + } +`; diff --git a/src/components/experimental/RadioGroup/RadioGroup.tsx b/src/components/experimental/RadioGroup/RadioGroup.tsx new file mode 100644 index 00000000..89d0eda5 --- /dev/null +++ b/src/components/experimental/RadioGroup/RadioGroup.tsx @@ -0,0 +1,15 @@ +import { RadioGroupProps as BaseRadioGroupProps } from 'react-aria-components'; +import React, { FC, ReactNode } from 'react'; + +import * as Styled from './RadioGroup.styled'; + +interface RadioGroupProps extends Omit { + children: ReactNode; + label: string; +} + +export const RadioGroup: FC = ({ label, children, ...props }) => ( + + {children} + +); diff --git a/src/components/experimental/RadioGroup/docs/RadioGroup.stories.tsx b/src/components/experimental/RadioGroup/docs/RadioGroup.stories.tsx new file mode 100644 index 00000000..1da92b2d --- /dev/null +++ b/src/components/experimental/RadioGroup/docs/RadioGroup.stories.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Meta, StoryObj } from '@storybook/react'; +import { RadioGroup } from '../RadioGroup'; +import { RadioButton } from '../../RadioButton/RadioButton'; + +const meta: Meta = { + title: 'Experimental/Components/RadioGroup', + component: RadioGroup, + args: { + children: [ + one, + two, + three + ], + label: 'Example', + defaultValue: 'one' + } +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Horizontal: Story = { + args: { + orientation: 'horizontal' + } +}; + +export const Invalid: Story = { + args: { + isInvalid: true + } +}; diff --git a/src/components/experimental/index.ts b/src/components/experimental/index.ts index 678e53e5..448d73f0 100644 --- a/src/components/experimental/index.ts +++ b/src/components/experimental/index.ts @@ -15,6 +15,8 @@ export { Label } from './Label/Label'; export { ListBox, ListBoxItem, LabelText, DescriptionText } from './ListBox/ListBox'; export { Modal } from './Modal/Modal'; export { Popover } from './Popover/Popover'; +export { RadioButton } from './RadioButton/RadioButton'; +export { RadioGroup } from './RadioGroup/RadioGroup'; export { Search } from './Search/Search'; export { Select } from './Select/Select'; export { Snackbar, SnackbarProps } from './Snackbar/Snackbar'; diff --git a/src/essentials/experimental/cssVariables.ts b/src/essentials/experimental/cssVariables.ts index 381d42d5..03b5fb6f 100644 --- a/src/essentials/experimental/cssVariables.ts +++ b/src/essentials/experimental/cssVariables.ts @@ -30,7 +30,7 @@ import { Join, Leaves } from '../../utils/types'; import { ColorPaletteSchema, SemanticColorsSchema } from './types'; const DS_PREFIX = 'wave-exp'; -type NameSpace = 'color' | 'palette'; +type NameSpace = 'color' | 'palette' | 'typescale'; type BareColorToken = Join, '-'>; type SemanticColorToken = Join, '-'>; diff --git a/src/essentials/experimental/globalStyles.ts b/src/essentials/experimental/globalStyles.ts index f2ea3118..7d5ef8e2 100644 --- a/src/essentials/experimental/globalStyles.ts +++ b/src/essentials/experimental/globalStyles.ts @@ -1,7 +1,7 @@ import { createGlobalStyle, css, CSSObject, GlobalStyleComponent, DefaultTheme } from 'styled-components'; import { TokenObject } from '../../utils/cssVariables'; -import { generateBareCssVariables, generateSemanticCssVariables } from './cssVariables'; +import { generateBareCssVariables, generateCssVariables, generateSemanticCssVariables } from './cssVariables'; import { SemanticColorsSchema } from './types'; export const DARK_THEME_CLASS = 'dark-scheme'; @@ -35,6 +35,15 @@ export const createThemeGlobalStyle = ( color-scheme: light; ${bareCssVariables} ${semanticCssVariablesForLightTheme} + ${generateCssVariables( + { + 'body-1-font': 'Roboto Flex', + 'body-1-size': '1rem', + 'body-1-weight': 400, + 'body-1-line-height': '1.5rem' + }, + 'typescale' + )} } .${DARK_THEME_CLASS} {