-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: 14263 create studio status radio group #14272
Open
wrt95
wants to merge
7
commits into
main
Choose a base branch
from
chore-14263-create-StudioStatusRadioGroup
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
c3fee83
chore: Implement StudioStatusRadioGroup
wrt95 3ae5828
chore: Create StudioRadio
wrt95 acdfb0b
update export
wrt95 b0e6593
Merge branch 'chore-14264-StudioRadio' into chore-14263-create-Studio…
wrt95 345d9fa
almost working
wrt95 ea0b56c
adding tests and finalising
wrt95 6845bf9
cleanup
wrt95 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
...studio-components/src/components/StudioStatusRadioGroup/StudioStatusRadioGroup.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,77 @@ | ||
.radioGroupContainer { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--fds-spacing-4); | ||
} | ||
|
||
.radioGroup { | ||
display: flex; | ||
gap: var(--fds-spacing-4); | ||
flex-wrap: wrap; | ||
} | ||
|
||
.radioButton { | ||
--radio-button-width: 240px; | ||
--border-width: 3px; | ||
|
||
display: flex; | ||
flex-direction: column; | ||
padding-block: var(--fds-spacing-2); | ||
padding-inline: var(--fds-spacing-4); | ||
border-radius: var(--fds-sizing-2); | ||
cursor: pointer; | ||
position: relative; | ||
width: var(--radio-button-width); | ||
border: var(--border-width) solid transparent; | ||
} | ||
|
||
.input { | ||
position: absolute; | ||
opacity: 0; | ||
pointer-events: none; | ||
} | ||
|
||
.radioButton:has(input:checked) { | ||
border-color: var(--fds-semantic-border-action-default); | ||
} | ||
|
||
.textContent { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--fds-spacing-4); | ||
} | ||
|
||
.title { | ||
font-weight: bold; | ||
text-align: left; | ||
} | ||
|
||
.text { | ||
word-wrap: break-word; | ||
word-break: break-word; | ||
white-space: normal; | ||
} | ||
|
||
.success { | ||
background-color: var(--fds-semantic-surface-success-subtle); | ||
} | ||
|
||
.success:hover { | ||
background-color: var(--fds-semantic-surface-success-subtle-hover); | ||
} | ||
|
||
.info { | ||
background-color: var(--fds-semantic-surface-action-first-subtle); | ||
} | ||
|
||
.info:hover { | ||
background-color: var(--fds-semantic-surface-action-first-subtle-hover); | ||
} | ||
|
||
.warning { | ||
background-color: var(--fds-semantic-surface-warning-subtle); | ||
} | ||
|
||
.warning:hover { | ||
background-color: var(--fds-semantic-surface-warning-subtle-hover); | ||
} |
48 changes: 48 additions & 0 deletions
48
...tudio-components/src/components/StudioStatusRadioGroup/StudioStatusRadioGroup.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,48 @@ | ||
import React, { useState } from 'react'; | ||
import type { Meta, StoryFn } from '@storybook/react'; | ||
import { StudioStatusRadioGroup, type StudioStatusRadioGroupProps } from './StudioStatusRadioGroup'; | ||
|
||
const options: StudioStatusRadioGroupProps['options'] = [ | ||
{ | ||
value: 'value1', | ||
title: 'Miljø 1', | ||
text: 'Sist publisert 11.06.2023 kl 14:03', | ||
color: 'success', | ||
}, | ||
{ | ||
value: 'value2', | ||
title: 'Miljø 2', | ||
text: 'Sist publisert 11.06.2023 kl 14:03', | ||
color: 'success', | ||
}, | ||
{ value: 'value3', title: 'Miljø 3', text: 'Forløpig ingen publiseringer', color: 'info' }, | ||
{ | ||
value: 'value4', | ||
title: 'Miljø 4', | ||
text: 'Applikasjonen er utilgjengelig i miljø', | ||
color: 'warning', | ||
}, | ||
]; | ||
|
||
type Story = StoryFn<typeof StudioStatusRadioGroup>; | ||
|
||
const meta: Meta = { | ||
title: 'Components/StudioStatusRadioGroup', | ||
component: StudioStatusRadioGroup, | ||
argTypes: {}, | ||
}; | ||
|
||
export const Preview: Story = () => { | ||
const [selectedValue, setSelectedValue] = useState<string | undefined>(); | ||
|
||
return ( | ||
<StudioStatusRadioGroup | ||
title='Velg et av alternativene under' | ||
options={options} | ||
onChange={(value) => setSelectedValue(value)} | ||
defaultValue={selectedValue} | ||
/> | ||
); | ||
}; | ||
|
||
export default meta; |
124 changes: 124 additions & 0 deletions
124
...s/studio-components/src/components/StudioStatusRadioGroup/StudioStatusRadioGroup.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,124 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { StudioStatusRadioGroup, type StudioStatusRadioGroupProps } from './StudioStatusRadioGroup'; | ||
|
||
const mockTitle1: string = 'Success'; | ||
const mockTitle2: string = 'Info'; | ||
const mockTitle3: string = 'Warning'; | ||
|
||
const mockText1: string = 'Success text'; | ||
const mockText2: string = 'Info text'; | ||
const mockText3: string = 'Warning text'; | ||
|
||
const mockValue1: string = 'success'; | ||
const mockValue2: string = 'info'; | ||
const mockValue3: string = 'warning'; | ||
|
||
const mockOption1: StudioStatusRadioGroupProps['options'][number] = { | ||
title: mockTitle1, | ||
text: mockText1, | ||
color: 'success', | ||
value: mockValue1, | ||
}; | ||
const mockOption2: StudioStatusRadioGroupProps['options'][number] = { | ||
title: mockTitle2, | ||
text: mockText2, | ||
color: 'info', | ||
value: mockValue2, | ||
}; | ||
const mockOption3: StudioStatusRadioGroupProps['options'][number] = { | ||
title: mockTitle3, | ||
text: mockText3, | ||
color: 'warning', | ||
value: mockValue3, | ||
}; | ||
|
||
const mockOptions: StudioStatusRadioGroupProps['options'] = [mockOption1, mockOption2, mockOption3]; | ||
const mockGroupTitle: string = 'Status group'; | ||
const mockOnChange = jest.fn(); | ||
|
||
const defaultProps: StudioStatusRadioGroupProps = { | ||
options: mockOptions, | ||
title: mockGroupTitle, | ||
onChange: mockOnChange, | ||
}; | ||
|
||
describe('StudioStatusRadioGroup', () => { | ||
it('renders radio buttons with titles and descriptions', () => { | ||
renderStudioStatusRadioGroup(); | ||
|
||
expect(screen.getByText(mockGroupTitle)).toBeInTheDocument(); | ||
expect(screen.getByText(mockTitle1)).toBeInTheDocument(); | ||
expect(screen.getByText(mockText1)).toBeInTheDocument(); | ||
expect(screen.getByText(mockTitle2)).toBeInTheDocument(); | ||
expect(screen.getByText(mockText2)).toBeInTheDocument(); | ||
expect(screen.getByText(mockTitle3)).toBeInTheDocument(); | ||
expect(screen.getByText(mockText3)).toBeInTheDocument(); | ||
}); | ||
|
||
it('allows selecting a radio button', async () => { | ||
const user = userEvent.setup(); | ||
renderStudioStatusRadioGroup(); | ||
|
||
const successRadioButton = screen.getByRole('radio', { name: `${mockTitle1} ${mockText1}` }); // Any way to do it without having both title and text? | ||
const infoRadioButton = screen.getByRole('radio', { name: `${mockTitle2} ${mockText2}` }); | ||
const warningRadioButton = screen.getByRole('radio', { name: `${mockTitle3} ${mockText3}` }); | ||
|
||
// Initially, no button should be selected | ||
expect(successRadioButton).not.toBeChecked(); | ||
expect(infoRadioButton).not.toBeChecked(); | ||
expect(warningRadioButton).not.toBeChecked(); | ||
|
||
await user.click(infoRadioButton); | ||
|
||
expect(infoRadioButton).toBeChecked(); | ||
expect(successRadioButton).not.toBeChecked(); | ||
expect(warningRadioButton).not.toBeChecked(); | ||
}); | ||
|
||
it('calls onChange with correct value when a radio button is selected', async () => { | ||
const user = userEvent.setup(); | ||
renderStudioStatusRadioGroup(); | ||
|
||
const infoRadioButton = screen.getByRole('radio', { name: `${mockTitle2} ${mockText2}` }); | ||
await user.click(infoRadioButton); | ||
|
||
expect(mockOnChange).toHaveBeenCalledWith(mockValue2); | ||
expect(mockOnChange).toHaveBeenCalledTimes(2); // Why is this being called twice? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @framitdavid, do you have a suggestion here? 🤔 |
||
}); | ||
|
||
it('renders with default value selected', () => { | ||
renderStudioStatusRadioGroup({ defaultValue: mockValue2 }); | ||
|
||
const successRadioButton = screen.getByRole('radio', { name: `${mockTitle1} ${mockText1}` }); | ||
const infoRadioButton = screen.getByRole('radio', { name: `${mockTitle2} ${mockText2}` }); | ||
|
||
expect(infoRadioButton).toBeChecked(); | ||
expect(successRadioButton).not.toBeChecked(); | ||
}); | ||
|
||
it('applies correct aria attributes for accessibility', () => { | ||
renderStudioStatusRadioGroup(); | ||
|
||
const successRadioButton = screen.getByRole('radio', { name: `${mockTitle1} ${mockText1}` }); | ||
const inputId = `${mockGroupTitle}-${mockValue1}`; | ||
|
||
expect(successRadioButton).toHaveAttribute('aria-labelledby', `${inputId}-title`); | ||
expect(successRadioButton).toHaveAttribute('aria-describedby', `${inputId}-text`); | ||
}); | ||
|
||
it('focuses on the radio button when clicked', async () => { | ||
const user = userEvent.setup(); | ||
renderStudioStatusRadioGroup(); | ||
|
||
const infoRadioButton = screen.getByRole('radio', { name: `${mockTitle2} ${mockText2}` }); | ||
await user.click(infoRadioButton); | ||
|
||
expect(infoRadioButton).toHaveFocus(); | ||
}); | ||
}); | ||
|
||
const renderStudioStatusRadioGroup = (props: Partial<StudioStatusRadioGroupProps> = {}) => { | ||
return render(<StudioStatusRadioGroup {...defaultProps} {...props} />); | ||
}; |
70 changes: 70 additions & 0 deletions
70
...d/libs/studio-components/src/components/StudioStatusRadioGroup/StudioStatusRadioGroup.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,70 @@ | ||
import React, { type ChangeEvent, type ReactElement } from 'react'; | ||
import classes from './StudioStatusRadioGroup.module.css'; | ||
import cn from 'classnames'; | ||
import { StudioLabelAsParagraph } from '../StudioLabelAsParagraph'; | ||
import { StudioParagraph } from '../StudioParagraph'; | ||
|
||
type StudioStatusRadioButtonColor = 'success' | 'info' | 'warning'; | ||
|
||
export type StudioStatusRadioButtonItem = { | ||
title: string; | ||
text: string; | ||
color: StudioStatusRadioButtonColor; | ||
value: string; | ||
}; | ||
|
||
export type StudioStatusRadioGroupProps = { | ||
options: StudioStatusRadioButtonItem[]; | ||
title: string; | ||
defaultValue?: string; | ||
onChange?: (value: string) => void; | ||
}; | ||
|
||
export const StudioStatusRadioGroup = ({ | ||
options, | ||
title: name, | ||
defaultValue, | ||
onChange, | ||
}: StudioStatusRadioGroupProps): ReactElement => { | ||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { | ||
if (onChange) { | ||
onChange(event.target.value); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className={classes.radioGroupContainer}> | ||
<StudioLabelAsParagraph size='md'>{name}</StudioLabelAsParagraph> | ||
<div className={classes.radioGroup} role='radiogroup'> | ||
{options.map(({ title, text, color, value }: StudioStatusRadioButtonItem) => { | ||
const inputId = `${name}-${value}`; | ||
const inputTitleId = `${inputId}-title`; | ||
const inputTextId = `${inputId}-text`; | ||
return ( | ||
<label key={value} className={cn(classes.radioButton, classes[color])}> | ||
<input | ||
type='radio' | ||
id={inputId} | ||
name={title} | ||
value={value} | ||
defaultChecked={defaultValue === value} | ||
onChange={handleChange} | ||
className={classes.input} | ||
aria-labelledby={inputTitleId} | ||
aria-describedby={inputTextId} | ||
/> | ||
<div className={classes.textContent}> | ||
<StudioLabelAsParagraph id={inputTitleId} className={classes.title} size='sm'> | ||
{title} | ||
</StudioLabelAsParagraph> | ||
<StudioParagraph id={inputTextId} className={classes.text} size='xs'> | ||
{text} | ||
</StudioParagraph> | ||
</div> | ||
</label> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
frontend/libs/studio-components/src/components/StudioStatusRadioGroup/index.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 @@ | ||
export { StudioStatusRadioGroup } from './StudioStatusRadioGroup'; |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@framitdavid, do you have a better suggestion here? 🤔