-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Radio, Fieldset): ✨ New
Radio
& Fieldset
component (unreleas…
…ed) (#666) Co-authored-by: Albertlarsen <[email protected]>
- Loading branch information
1 parent
75b8bd7
commit e2d6f89
Showing
24 changed files
with
1,274 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
|
||
display: inline-block; | ||
margin: 0; | ||
padding: 0; | ||
color: var(--fds-semantic-text-neutral-default); | ||
} | ||
|
||
|
28 changes: 28 additions & 0 deletions
28
packages/react/src/components/form/Fieldset/Fieldset.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
.fieldset { | ||
margin: 0; | ||
padding: 0; | ||
border: 0; | ||
min-width: 0; | ||
} | ||
|
||
.fieldset > :not(:first-child, :empty) { | ||
margin-top: var(--fds-spacing-2); | ||
} | ||
|
||
.description { | ||
color: var(--fds-semantic-text-neutral-subtle); | ||
font-weight: 400; | ||
} | ||
|
||
.readonly { | ||
opacity: 0.3; | ||
} | ||
|
||
.readonly > .legend { | ||
display: inline-flex; | ||
} | ||
|
||
.disabled > .legend, | ||
.disabled > .description { | ||
color: var(--fds-semantic-border-neutral-subtle); | ||
} |
51 changes: 51 additions & 0 deletions
51
packages/react/src/components/form/Fieldset/Fieldset.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
|
||
import { Fieldset } from './Fieldset'; | ||
|
||
describe('Fieldset', () => { | ||
test('has correct legend and description', () => { | ||
render( | ||
<Fieldset | ||
legend='test legend' | ||
description='test description' | ||
></Fieldset>, | ||
); | ||
const fieldset = screen.getByRole('group', { name: 'test legend' }); | ||
expect(fieldset).toBeDefined(); | ||
expect(fieldset).toHaveAccessibleDescription('test description'); | ||
}); | ||
test('is described by error message and invalid', () => { | ||
render( | ||
<Fieldset | ||
legend='test legend' | ||
description='test description' | ||
error='test error' | ||
></Fieldset>, | ||
); | ||
|
||
const errorFieldset = screen.getByRole('group', { | ||
description: 'test description test error', | ||
}); | ||
expect(errorFieldset).toBeDefined(); | ||
expect(errorFieldset).toHaveAccessibleDescription( | ||
'test description test error', | ||
); | ||
expect(errorFieldset).toBeInvalid(); | ||
}); | ||
test('and its children are disabled', () => { | ||
render( | ||
<Fieldset disabled> | ||
<input | ||
value='test' | ||
readOnly | ||
/> | ||
</Fieldset>, | ||
); | ||
|
||
const input = screen.getByDisplayValue<HTMLInputElement>('test'); | ||
|
||
expect(input).toBeDisabled(); | ||
expect(screen.getByRole('group')).toBeDisabled(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import type { FieldsetHTMLAttributes, ReactNode } from 'react'; | ||
import React, { useContext, forwardRef, createContext } from 'react'; | ||
import cn from 'classnames'; | ||
import { PadlockLockedFillIcon } from '@navikt/aksel-icons'; | ||
|
||
import { Label, Paragraph, ErrorMessage } from '../../Typography'; | ||
|
||
import { useFieldset } from './useFieldset'; | ||
import classes from './Fieldset.module.css'; | ||
|
||
export type FieldsetContextType = { | ||
error?: ReactNode; | ||
errorId?: string; | ||
disabled?: boolean; | ||
readOnly?: boolean; | ||
size?: 'xsmall' | 'small' | 'medium'; | ||
}; | ||
|
||
export const FieldsetContext = createContext<FieldsetContextType | null>(null); | ||
|
||
export type FieldsetProps = { | ||
/** A description of the fieldset. This will appear below the legend. */ | ||
description?: ReactNode; | ||
/** Toggle `disabled` all input fields within the fieldset. */ | ||
disabled?: boolean; | ||
/** If set, this will diplay an error message at the bottom of the fieldset. */ | ||
error?: ReactNode; | ||
/** The legend of the fieldset. */ | ||
legend?: ReactNode; | ||
/** The size of the fieldset. */ | ||
size?: 'xsmall' | 'small' | 'medium'; | ||
/** Toggle `readOnly` on fieldset context. | ||
* @note This does not prevent fieldset values from being submited */ | ||
readOnly?: boolean; | ||
} & FieldsetHTMLAttributes<HTMLFieldSetElement>; | ||
|
||
export const Fieldset = forwardRef<HTMLFieldSetElement, FieldsetProps>( | ||
(props, ref) => { | ||
const { children, legend, description, error, ...rest } = props; | ||
|
||
const { fieldsetProps, size, readOnly, errorId, hasError, descriptionId } = | ||
useFieldset(props); | ||
|
||
const fieldset = useContext(FieldsetContext); | ||
|
||
return ( | ||
<FieldsetContext.Provider | ||
value={{ | ||
error: error ?? fieldset?.error, | ||
errorId: hasError ? errorId : undefined, | ||
size, | ||
disabled: props?.disabled, | ||
readOnly, | ||
}} | ||
> | ||
<fieldset | ||
{...rest} | ||
{...fieldsetProps} | ||
className={cn( | ||
classes.fieldset, | ||
readOnly && classes.readonly, | ||
props?.disabled && classes.disabled, | ||
rest.className, | ||
)} | ||
ref={ref} | ||
> | ||
<Label | ||
as='legend' | ||
size={size} | ||
className={classes.legend} | ||
> | ||
{readOnly && <PadlockLockedFillIcon />} | ||
{legend} | ||
</Label> | ||
{description && ( | ||
<Paragraph | ||
id={descriptionId} | ||
className={classes.description} | ||
size={size} | ||
as='div' | ||
short | ||
> | ||
{description} | ||
</Paragraph> | ||
)} | ||
{children} | ||
<div | ||
id={errorId} | ||
aria-live='polite' | ||
aria-relevant='additions removals' | ||
> | ||
{hasError && <ErrorMessage size={size}>{error}</ErrorMessage>} | ||
</div> | ||
</fieldset> | ||
</FieldsetContext.Provider> | ||
); | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Fieldset'; |
17 changes: 17 additions & 0 deletions
17
packages/react/src/components/form/Fieldset/useFieldset.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { useFormField } from '../useFormField'; | ||
|
||
import type { FieldsetProps } from './Fieldset'; | ||
|
||
/** Handles fieldset props and state */ | ||
export const useFieldset = (props: FieldsetProps) => { | ||
const formField = useFormField(props, 'fieldset'); | ||
const { inputProps } = formField; | ||
|
||
return { | ||
...formField, | ||
fieldsetProps: { | ||
'aria-invalid': inputProps['aria-invalid'], | ||
'aria-describedby': inputProps['aria-describedby'], | ||
}, | ||
}; | ||
}; |
11 changes: 11 additions & 0 deletions
11
packages/react/src/components/form/Radio/Group/Group.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.xsmall, | ||
.small, | ||
.medium { | ||
margin-left: calc(var(--fds-spacing-2) * -1); | ||
} | ||
|
||
.inline { | ||
display: flex; | ||
flex-direction: row; | ||
gap: var(--fds-spacing-2); | ||
} |
106 changes: 106 additions & 0 deletions
106
packages/react/src/components/form/Radio/Group/Group.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
import React, { useState } from 'react'; | ||
import type { Meta, StoryFn } from '@storybook/react'; | ||
|
||
import { Button, Paragraph } from '../../..'; | ||
import { Radio } from '../'; | ||
|
||
export default { | ||
title: 'ikke utgitt/Radio/Group', | ||
component: Radio.Group, | ||
parameters: { | ||
status: { | ||
type: 'beta', | ||
url: 'http://www.url.com/status', | ||
}, | ||
}, | ||
} as Meta; | ||
|
||
export const Preview: StoryFn<typeof Radio.Group> = (args) => ( | ||
<Radio.Group {...args}> | ||
<Radio value='vanilje'>Vanilje</Radio> | ||
<Radio value='jordbær'>Jordbær</Radio> | ||
<Radio value='sjokolade'>Sjokolade</Radio> | ||
<Radio value='spiser-ikke-is'>Jeg spiser ikke iskrem</Radio> | ||
</Radio.Group> | ||
); | ||
|
||
Preview.args = { | ||
legend: 'Hvilken iskremsmak er best?', | ||
description: 'Velg din favorittsmak blant alternativene.', | ||
readOnly: false, | ||
disabled: false, | ||
error: '', | ||
}; | ||
|
||
export const Error: StoryFn<typeof Radio> = () => ( | ||
<Radio.Group | ||
legend='Velg pizza (påkreved)' | ||
description='Alle pizzaene er laget på våre egne nybakte bunner og serveres med kokkens egen osteblanding og tomatsaus.' | ||
error='Du må velge en av våre pizzaer for å legge inn bestilling' | ||
> | ||
<Radio value='ost'>Bare ost</Radio> | ||
<Radio | ||
value='Dobbeldekker' | ||
description='Chorizo spesial med kokkens luksuskylling' | ||
> | ||
Dobbeldekker | ||
</Radio> | ||
<Radio value='flammen'>Flammen</Radio> | ||
<Radio value='snadder'>Snadder</Radio> | ||
</Radio.Group> | ||
); | ||
|
||
export const Controlled: StoryFn<typeof Radio> = () => { | ||
const [value, setValue] = useState<string>(); | ||
|
||
return ( | ||
<> | ||
<span style={{ display: 'flex', gap: '1rem' }}> | ||
<Button onClick={() => setValue('flammen')}>Velg Flammen</Button> | ||
<Button onClick={() => setValue('snadder')}>Velg Snadder</Button> | ||
<Paragraph spacing>Du har valgt: {value}</Paragraph> | ||
</span> | ||
<Radio.Group | ||
legend='Velg pizza (påkreved)' | ||
description='Alle pizzaene er laget på våre egne nybakte bunner og serveres med kokkens egen osteblanding og tomatsaus.' | ||
value={value} | ||
onChange={(e) => setValue(e.target.value)} | ||
> | ||
<Radio value='ost'>Bare ost</Radio> | ||
<Radio | ||
value='Dobbeldekker' | ||
description='Chorizo spesial med kokkens luksuskylling' | ||
> | ||
Dobbeldekker | ||
</Radio> | ||
<Radio value='flammen'>Flammen</Radio> | ||
<Radio value='snadder'>Snadder</Radio> | ||
</Radio.Group> | ||
</> | ||
); | ||
}; | ||
|
||
export const ReadOnly = Preview.bind({}); | ||
|
||
ReadOnly.args = { | ||
...Preview.args, | ||
readOnly: true, | ||
}; | ||
|
||
export const Disabled = Preview.bind({}); | ||
|
||
Disabled.args = { | ||
...Preview.args, | ||
disabled: true, | ||
}; | ||
|
||
export const Inline: StoryFn<typeof Radio.Group> = () => ( | ||
<Radio.Group | ||
legend='Kontaktes på epost?' | ||
description='Bekreft om du ønsker å bli kontaktet per epost. ' | ||
inline | ||
> | ||
<Radio value='ja'>Ja</Radio> | ||
<Radio value='nei'>Nei</Radio> | ||
</Radio.Group> | ||
); |
Oops, something went wrong.