Co-authored-by: dev_agent <dev_agent@cubecraftlabs.com>
This commit was merged in pull request #86.
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--color-neutral-700);
|
||||
}
|
||||
|
||||
.control {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.select {
|
||||
appearance: none;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-width: 44px;
|
||||
height: 56px;
|
||||
padding: 0 36px 0 16px;
|
||||
border: 1px solid var(--color-neutral-300);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.select:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* The chevron sits over the select's right padding and lets taps through. */
|
||||
.chevron {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 14px;
|
||||
transform: translateY(-50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { expectMinHitArea } from '../test/hitArea.ts'
|
||||
import { Select } from './Select.tsx'
|
||||
|
||||
describe('Select', () => {
|
||||
it('is a native select named by its label', () => {
|
||||
const onChange = vi.fn()
|
||||
render(
|
||||
<Select label="Provider" value="a" onChange={onChange}>
|
||||
<option value="a">Alpha</option>
|
||||
<option value="b">Beta</option>
|
||||
</Select>,
|
||||
)
|
||||
const select = screen.getByRole('combobox', { name: 'Provider' })
|
||||
expect(select).toHaveValue('a')
|
||||
fireEvent.change(select, { target: { value: 'b' } })
|
||||
expect(onChange).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('keeps a 44 px tap target', () => {
|
||||
render(
|
||||
<Select label="Model" defaultValue="a">
|
||||
<option value="a">Alpha</option>
|
||||
</Select>,
|
||||
)
|
||||
expectMinHitArea(screen.getByRole('combobox', { name: 'Model' }))
|
||||
})
|
||||
|
||||
it('hides its chevron from assistive technology', () => {
|
||||
const { container } = render(
|
||||
<Select label="Model" defaultValue="a">
|
||||
<option value="a">Alpha</option>
|
||||
</Select>,
|
||||
)
|
||||
expect(container.querySelector('svg')).toHaveAttribute('aria-hidden', 'true')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
import { useId, type ComponentPropsWithRef } from 'react'
|
||||
import { Icon } from './Icon.tsx'
|
||||
import styles from './Select.module.css'
|
||||
|
||||
export type SelectProps = Omit<ComponentPropsWithRef<'select'>, 'id' | 'multiple'> & {
|
||||
label: string
|
||||
}
|
||||
|
||||
// Pill dropdown: a native select on the surface fill with a chevron, always
|
||||
// associated with its label. Pass the options as children.
|
||||
export function Select({ label, className, children, ...rest }: SelectProps) {
|
||||
const id = useId()
|
||||
return (
|
||||
<div className={styles.field}>
|
||||
<label htmlFor={id} className={styles.label}>
|
||||
{label}
|
||||
</label>
|
||||
<span className={styles.control}>
|
||||
<select id={id} className={[styles.select, className].filter(Boolean).join(' ')} {...rest}>
|
||||
{children}
|
||||
</select>
|
||||
<Icon icon={ChevronDown} className={styles.chevron} />
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export { ListRow, type ListRowProps } from './ListRow.tsx'
|
||||
export { Photo, type PhotoProps } from './Photo.tsx'
|
||||
export { Segmented, type SegmentedOption, type SegmentedProps } from './Segmented.tsx'
|
||||
export { Sheet, type SheetProps } from './Sheet.tsx'
|
||||
export { Select, type SelectProps } from './Select.tsx'
|
||||
export { Skeleton, type SkeletonProps } from './Skeleton.tsx'
|
||||
export { Slider, type SliderProps } from './Slider.tsx'
|
||||
export { Tag, type TagProps, type TagTone } from './Tag.tsx'
|
||||
|
||||
Reference in New Issue
Block a user