-
Notifications
You must be signed in to change notification settings - Fork 2
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
Add new Select
component
#828
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import classnames from 'classnames'; | ||
import type { ComponentChildren, JSX } from 'preact'; | ||
|
||
import type { PresentationalProps } from '../../types'; | ||
|
||
import InputRoot from './InputRoot'; | ||
|
||
type ComponentProps = { | ||
children?: ComponentChildren; | ||
hasError?: boolean; | ||
}; | ||
export type SelectProps = PresentationalProps & | ||
ComponentProps & | ||
JSX.HTMLAttributes<HTMLSelectElement>; | ||
|
||
// URI-encoded source of `CaretDownIcon` | ||
// Currently, the color (stroke) is hard-coded | ||
const arrowImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16' aria-hidden='true' focusable='false'%3E%3Cg fill-rule='evenodd'%3E%3Crect fill='none' stroke='none' x='0' y='0' width='16' height='16'%3E%3C/rect%3E%3Cpath fill='none' stroke='%239c9c9c' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M12 6l-4 4-4-4'%3E%3C/path%3E%3C/g%3E%3C/svg%3E")`; | ||
|
||
/** | ||
* Style a native `<select>` element. | ||
*/ | ||
const SelectNext = function Select({ | ||
children, | ||
classes, | ||
hasError, | ||
type = 'text', | ||
|
||
...htmlAttributes | ||
}: SelectProps) { | ||
return ( | ||
<InputRoot | ||
classes={classnames( | ||
'appearance-none h-touch-minimum', | ||
// position the down-arrow image centered at the right, offset from the | ||
// right edge by 0.5rem. Arrow image width (4 units) + horizontal | ||
// padding (3 units) = 7 units of right padding needed. | ||
'bg-no-repeat bg-[center_right_0.5rem] pr-7', | ||
classes | ||
)} | ||
element="select" | ||
type={type} | ||
hasError={hasError} | ||
style={{ | ||
backgroundImage: arrowImage, | ||
}} | ||
Comment on lines
+44
to
+46
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. The LMS implementation of this The advantage of using a background image is that no wrapping container is needed. This allows |
||
{...htmlAttributes} | ||
data-component="Select" | ||
> | ||
{children} | ||
</InputRoot> | ||
); | ||
}; | ||
|
||
export default SelectNext; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { mount } from 'enzyme'; | ||
|
||
import { testPresentationalComponent } from '../../test/common-tests'; | ||
|
||
import Select from '../Select'; | ||
|
||
const contentFn = (Component, props = {}) => { | ||
return mount(<Component aria-label="Test input" {...props} />); | ||
}; | ||
|
||
describe('Select', () => { | ||
testPresentationalComponent(Select, { createContent: contentFn }); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ export { | |
IconButton, | ||
Input, | ||
InputGroup, | ||
Select, | ||
} from './components/input'; | ||
export { | ||
Card, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { | ||
ArrowLeftIcon, | ||
ArrowRightIcon, | ||
IconButton, | ||
InputGroup, | ||
Select, | ||
} from '../../../../next'; | ||
import Library from '../../Library'; | ||
import Next from '../../LibraryNext'; | ||
|
||
import type { SelectProps } from '../../../../components/input/Select'; | ||
|
||
function SelectWrapper({ children, ...selectProps }: SelectProps) { | ||
const options = children ?? ( | ||
<> | ||
<option value={-1}>All students</option> | ||
<option value="a">Albert Banana</option> | ||
<option value="b">Bernard California</option> | ||
<option value="c">Cecelia Davenport</option> | ||
<option value="d">Doris Evanescence</option> | ||
</> | ||
); | ||
return <Select {...selectProps}>{options}</Select>; | ||
} | ||
|
||
export default function SelectPage() { | ||
return ( | ||
<Library.Page | ||
title="Select" | ||
intro={ | ||
<p> | ||
<code>Select</code> is a presentational component that styles native{' '} | ||
<code>{'<select>'}</code> elements. | ||
</p> | ||
} | ||
> | ||
<Library.Section | ||
title="Select" | ||
intro={ | ||
<p> | ||
<code>Select</code> styles <code>{'<select>'}</code> elements. Note | ||
that <code>{'<option>'}</code> elements, with a few browser-specific | ||
exceptions, cannot be styled with CSS. | ||
</p> | ||
} | ||
> | ||
<Library.Pattern title="Status"> | ||
<p> | ||
<code>Select</code> is a new component. | ||
</p> | ||
</Library.Pattern> | ||
<Library.Pattern title="Usage"> | ||
<Next.Usage componentName="Select" /> | ||
|
||
<Library.Example> | ||
<Library.Demo title="Basic Select" withSource> | ||
<div> | ||
<SelectWrapper aria-label="Example input"> | ||
<option value={-1}>All students</option> | ||
<option value="a">Albert Banana</option> | ||
<option value="b">Bernard California</option> | ||
<option value="c">Cecelia Davenport</option> | ||
</SelectWrapper> | ||
</div> | ||
</Library.Demo> | ||
|
||
<Library.Demo title="Setting Select width" withSource> | ||
<div className="w-[250px]"> | ||
<SelectWrapper aria-label="Example input" /> | ||
</div> | ||
</Library.Demo> | ||
|
||
<Library.Demo title="Select in an InputGroup" withSource> | ||
<div className="w-[380px]"> | ||
<InputGroup> | ||
<IconButton | ||
icon={ArrowLeftIcon} | ||
title="Previous student" | ||
variant="dark" | ||
/> | ||
<SelectWrapper aria-label="Example input" /> | ||
<IconButton | ||
icon={ArrowRightIcon} | ||
title="Next student" | ||
variant="dark" | ||
/> | ||
</InputGroup> | ||
</div> | ||
</Library.Demo> | ||
</Library.Example> | ||
</Library.Pattern> | ||
|
||
<Library.Pattern title="Props"> | ||
<Library.Example title="hasError"> | ||
<p> | ||
Set <code>hasError</code> to indicate that there is an associated | ||
error. | ||
</p> | ||
<Library.Demo withSource> | ||
<div className="w-[350px]"> | ||
<SelectWrapper aria-label="Example input" hasError /> | ||
</div> | ||
</Library.Demo> | ||
</Library.Example> | ||
|
||
<Library.Example title="disabled"> | ||
<Library.Demo withSource> | ||
<div className="w-[350px]"> | ||
<SelectWrapper aria-label="Example input" disabled /> | ||
</div> | ||
</Library.Demo> | ||
</Library.Example> | ||
</Library.Pattern> | ||
</Library.Section> | ||
</Library.Page> | ||
); | ||
} |
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.
Alternately, we can extend the Tailwind theme in the package's preset:
which could then be applied with the
bg-caret-down
utility class.Putting the source of the image here in the component module means we could ostensibly set
stroke
color or other attribute values dynamically. The up side of putting it in the tailwind preset is that, ostensibly, a consumer could override it with something completely different.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.
I guess there are pros and cons to both approaches, but since it's hard to see how the needs for this component will evolve, I would suggest keeping it like this and change it later if wee see the need.