Skip to content
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

feat: Select component #1151

Merged
merged 34 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3284ad
Scaffold and PoC
dlnr Mar 22, 2024
efa5754
Add select dropdown arrow icon
dlnr Mar 26, 2024
d2378ac
Merge branch 'develop' of https://github.com/Amsterdam/design-system …
dlnr Mar 27, 2024
38cd487
remove test multiple attribute
dlnr Mar 27, 2024
e883540
Update ref type in Select.test.tsx
dlnr Mar 27, 2024
989046f
Merge branch 'develop' of https://github.com/Amsterdam/design-system …
dlnr Apr 4, 2024
d1a2221
Add new options to Select component
dlnr Apr 4, 2024
7e82d31
Refactor select component styles and add support for invalid state
dlnr Apr 4, 2024
9ed291a
Add disabled state to select component
dlnr Apr 4, 2024
9c9a30e
Disabled background image
dlnr Apr 4, 2024
7cb6d22
Merge branch 'develop' of https://github.com/Amsterdam/design-system …
dlnr Apr 4, 2024
e813e4f
Adding tests
dlnr Apr 4, 2024
4b9a992
Update select component styles and add new stories
dlnr Apr 4, 2024
f8e7a10
Added optgroup with stories and test
dlnr Apr 5, 2024
1c8f21f
Add aria-invalid attribute to Select component
dlnr Apr 5, 2024
2ccb18d
Add keyboard navigation docs
dlnr Apr 5, 2024
80e59ba
Remove keyboard navigation reference from select component README
dlnr Apr 5, 2024
ede6fa9
Update keyboard navigation in select component
dlnr Apr 5, 2024
12c9e92
Merge branch 'develop' into feature/DES-662-Select
VincentSmedinga Apr 5, 2024
4a7a318
Merge branch 'develop' into feature/DES-662-Select
VincentSmedinga Apr 10, 2024
fc97cde
Fix select component styling and tokens
dlnr Apr 11, 2024
6d47507
Update keyboard navigation in select component
dlnr Apr 11, 2024
1922aa9
Removed value and placeholder options
dlnr Apr 12, 2024
f3e2aa4
Update select component styling and tokens
dlnr Apr 12, 2024
bbc631a
Removed multiple story
dlnr Apr 12, 2024
0ca5da2
Refactor Select component stories and remove unused options
dlnr Apr 12, 2024
72a2fbf
max-inline-size restored
dlnr Apr 12, 2024
6db4ffe
Update select component styling and tokens
dlnr Apr 16, 2024
cb8b6f9
Update select component styling and tokens
dlnr Apr 17, 2024
46adee1
Update select component styling and tokens
dlnr Apr 19, 2024
7a978d2
Update padding-inline value in select.tokens.json
dlnr Apr 19, 2024
a0cdc8f
Refactor Select component stories and remove unused options
dlnr Apr 19, 2024
06512f4
Merge branch 'develop' into feature/DES-662-Select
alimpens Apr 19, 2024
628023d
Update packages/css/src/components/index.scss
alimpens Apr 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/css/src/components/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

/* Append here */
@import "./select/select";
@import "./document/document";
@import "./avatar/avatar";
@import "./form-field-character-counter/form-field-character-counter";
Expand Down
9 changes: 9 additions & 0 deletions packages/css/src/components/select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- @license CC0-1.0 -->

# Select

A form control that allows users to select one or more options from a list.

## References

- [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select)
55 changes: 55 additions & 0 deletions packages/css/src/components/select/select.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

@mixin reset {
appearance: none;
border: 0;
}
VincentSmedinga marked this conversation as resolved.
Show resolved Hide resolved

.ams-select {
background-color: var(--ams-select-background-color);
box-shadow: var(--ams-select-box-shadow);
color: var(--ams-select-color);
font-family: var(--ams-select-font-family);
font-size: var(--ams-select-font-size);
font-weight: var(--ams-select-font-weight);
line-height: var(--ams-select-line-height);
max-inline-size: var(--ams-select-max-inline-size);
alimpens marked this conversation as resolved.
Show resolved Hide resolved
outline-offset: var(--ams-select-outline-offset);
padding-block: var(--ams-select-padding-block);
padding-inline: var(--ams-select-padding-inline);
touch-action: manipulation;

&:not([multiple]) {
background-image: var(--ams-select-background-image);
background-position: var(--ams-select-background-position);
background-repeat: no-repeat;
background-size: 1em 1em;
}

&:hover {
box-shadow: var(--ams-select-hover-box-shadow);
}

@include reset;
}

.ams-select[aria-invalid="true"] {
box-shadow: var(--ams-select-invalid-box-shadow);

&:hover {
box-shadow: var(--ams-select-invalid-hover-box-shadow);
}
}

.ams-select:disabled {
box-shadow: var(--ams-select-disabled-box-shadow);
color: var(--ams-select-disabled-color);
cursor: not-allowed;

&:not([multiple]) {
background-image: var(--ams-select-disabled-background-image);
}
}
5 changes: 5 additions & 0 deletions packages/react/src/Select/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<!-- @license CC0-1.0 -->

# React Select component

[Select documentation](../../../css/src/components/select/README.md)
90 changes: 90 additions & 0 deletions packages/react/src/Select/Select.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { Select } from './Select'
import '@testing-library/jest-dom'

describe('Select', () => {
it('renders', () => {
render(<Select />)

const component = screen.getByRole('combobox')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders a design system BEM class name', () => {
render(<Select />)

const component = screen.getByRole('combobox')

expect(component).toHaveClass('ams-select')
})

it('renders an additional class name', () => {
render(<Select className="extra" />)

const component = screen.getByRole('combobox')

expect(component).toHaveClass('ams-select extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLSelectElement>()

render(<Select ref={ref} />)

const component = screen.getByRole('combobox')

expect(ref.current).toBe(component)
})

it('renders options', () => {
render(
<Select>
<Select.Option value="a">Option A</Select.Option>
<Select.Option value="b">Option B</Select.Option>
</Select>,
)

const select = screen.getByRole('combobox')

const option = screen.getByRole('option', {
name: 'Option B',
})

expect(select).toContain(option)
})

it('can be disabled', () => {
render(<Select disabled />)

const component = screen.getByRole('combobox')

expect(component).toBeDisabled()
})

it('can be invalid', () => {
render(<Select invalid />)

const component = screen.getByRole('combobox')

expect(component).toHaveClass('ams-select--invalid')
})

it('is not required by default', () => {
render(<Select />)

const component = screen.getByRole('combobox')

expect(component).not.toBeRequired()
})

it('omits the required attribute when not required', () => {
render(<Select required={false} />)

const component = screen.getByRole('combobox')

expect(component).not.toHaveAttribute('required')
})
})
32 changes: 32 additions & 0 deletions packages/react/src/Select/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, PropsWithChildren, SelectHTMLAttributes } from 'react'
import { SelectOption } from './SelectOption'
import { SelectOptionGroup } from './SelectOptionGroup'

export type SelectProps = {
/** There is no native invalid attribute for select, but you can use this to get the same result as other form components */
invalid?: boolean
} & PropsWithChildren<SelectHTMLAttributes<HTMLSelectElement>>

const SelectRoot = forwardRef(
({ children, className, invalid, ...restProps }: SelectProps, ref: ForwardedRef<HTMLSelectElement>) => (
<select
{...restProps}
ref={ref}
className={clsx('ams-select', invalid && 'ams-select--invalid', className)}
aria-invalid={invalid || undefined}
>
{children}
</select>
),
)

SelectRoot.displayName = 'Select'

export const Select = Object.assign(SelectRoot, { Option: SelectOption, Group: SelectOptionGroup })
59 changes: 59 additions & 0 deletions packages/react/src/Select/SelectOption.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { Select } from './Select'
import '@testing-library/jest-dom'

describe('Select option', () => {
it('renders', () => {
render(<Select.Option />)

const component = screen.getByRole('option')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders an option role element with a text label', () => {
render(<Select.Option>Option</Select.Option>)

const option = screen.getByRole('option', {
name: 'Option',
})

expect(option).toBeInTheDocument()
})

it('renders a design system BEM class name', () => {
render(<Select.Option />)

const component = screen.getByRole('option')

expect(component).toHaveClass('ams-select__option')
})

it('renders an additional class name', () => {
render(<Select.Option className="extra" />)

const component = screen.getByRole('option')

expect(component).toHaveClass('ams-select__option extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLOptionElement>()

render(<Select.Option ref={ref} />)

const component = screen.getByRole('option')

expect(ref.current).toBe(component)
})

it('can be disabled', () => {
render(<Select.Option disabled />)

const component = screen.getByRole('option')

expect(component).toBeDisabled()
})
})
23 changes: 23 additions & 0 deletions packages/react/src/Select/SelectOption.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, OptionHTMLAttributes, PropsWithChildren } from 'react'

export type SelectOptionProps = {} & OptionHTMLAttributes<HTMLOptionElement>
alimpens marked this conversation as resolved.
Show resolved Hide resolved

export const SelectOption = forwardRef(
(
{ children, className, ...restProps }: PropsWithChildren<SelectOptionProps>,
ref: ForwardedRef<HTMLOptionElement>,
) => (
<option {...restProps} ref={ref} className={clsx('ams-select__option', className)}>
{children}
</option>
),
)

SelectOption.displayName = 'Select.Option'
59 changes: 59 additions & 0 deletions packages/react/src/Select/SelectOptionGroup.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { render, screen } from '@testing-library/react'
import { createRef } from 'react'
import { Select } from './Select'
import '@testing-library/jest-dom'

describe('Select', () => {
alimpens marked this conversation as resolved.
Show resolved Hide resolved
it('renders', () => {
render(<Select.Group />)

const component = screen.getByRole('group')

expect(component).toBeInTheDocument()
expect(component).toBeVisible()
})

it('renders an group role element with a text label', () => {
render(<Select.Group label="Options" />)

const option = screen.getByRole('group', {
name: 'Options',
})

expect(option).toBeInTheDocument()
})

it('renders a design system BEM class name', () => {
render(<Select.Group />)

const component = screen.getByRole('group')

expect(component).toHaveClass('ams-select__group')
})

it('renders an additional class name', () => {
render(<Select.Group className="extra" />)

const component = screen.getByRole('group')

expect(component).toHaveClass('ams-select__group extra')
})

it('supports ForwardRef in React', () => {
const ref = createRef<HTMLOptGroupElement>()

render(<Select.Group ref={ref} />)

const component = screen.getByRole('group')

expect(ref.current).toBe(component)
})

it('can be disabled', () => {
render(<Select.Group disabled />)

const component = screen.getByRole('group')

expect(component).toBeDisabled()
})
})
23 changes: 23 additions & 0 deletions packages/react/src/Select/SelectOptionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @license EUPL-1.2+
* Copyright Gemeente Amsterdam
*/

import clsx from 'clsx'
import { forwardRef } from 'react'
import type { ForwardedRef, OptgroupHTMLAttributes, PropsWithChildren } from 'react'

export type SelectOptionGroupProps = OptgroupHTMLAttributes<HTMLOptGroupElement>

export const SelectOptionGroup = forwardRef(
(
{ children, className, ...restProps }: PropsWithChildren<SelectOptionGroupProps>,
ref: ForwardedRef<HTMLOptGroupElement>,
) => (
<optgroup {...restProps} ref={ref} className={clsx('ams-select__group', className)}>
{children}
</optgroup>
),
)

SelectOptionGroup.displayName = 'Select.Group'
3 changes: 3 additions & 0 deletions packages/react/src/Select/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Select } from './Select'
export type { SelectProps } from './Select'
export type { SelectOptionProps } from './SelectOption'
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

/* Append here */
export * from './Select'
export * from './Avatar'
export * from './FormFieldCharacterCounter'
export * from './DescriptionList'
Expand Down
Loading
Loading