Skip to content
Permalink
Browse files
Add select component
  • Loading branch information
AbdallahAbis committed Sep 14, 2022
1 parent 01c2d0a commit 41d1db977a9c122b201a03d29b3827a3d6e6cd58
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 1 deletion.
@@ -0,0 +1,10 @@
import { render } from '@testing-library/react';

import Button from './index';

describe('Button', () => {
it('should render successfully', () => {
const { baseElement } = render(<Button />);
expect(baseElement).toBeTruthy();
});
});
@@ -0,0 +1,15 @@
import React from 'react'

import Select from './index';

export default {
title: 'Select ',
component: Select
}
export const Basic = () => (
<div className='bg-gray-100 h-screen flex justify-center pt-24'>
<div className='w-full md:max-w-max'>
<Select label='Choose an option:' />
</div>
</div>
)
@@ -0,0 +1,141 @@
import React, { useState } from 'react'
import { useSelect } from 'downshift'
import { w } from 'windstitch'
import Text from '../Text'

const classNames = (...classes: string[]) => classes.filter(Boolean).join(' ');
const data = [
{ id: 1, title: 'Wade Cooper', secondaryText: '@wadecooper', online: true },
{ id: 2, title: 'Arlene Mccoy', secondaryText: '@arlenemccoy', online: false },
{ id: 3, title: 'Devon Webb', secondaryText: '@devonwebb', online: false },
{ id: 4, title: 'Tom Cook', secondaryText: '@tomcook', online: true },
{ id: 5, title: 'Tanya Fox', secondaryText: '@tanyafox', online: false },
{ id: 6, title: 'Hellen Schmidt', secondaryText: '@hellenschmidt', online: true },
{ id: 7, title: 'Caroline Schultz', secondaryText: '@carolineschultz', online: true },
{ id: 8, title: 'Mason Heaney', secondaryText: '@masonheaney', online: false },
{ id: 9, title: 'Claudie Smitham', secondaryText: '@claudiesmitham', online: true },
{ id: 10, title: 'Emil Schaefer', secondaryText: '@emilschaefer', online: false },
]
function itemToString(item) {
return item ? item.title : ''
}

const ChevronUpDownIcon = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</svg>
const CheckIcon = (props) => <svg {...props} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>


const size = {
xs: 'max-w-full md:max-w-[10rem]',
sm: 'max-w-full md:max-w-[15rem]',
md: 'max-w-full md:max-w-xs',
lg: 'max-w-full md:max-w-sm',
xl: 'max-w-full md:max-w-md',
'2xl': 'max-w-full md:max-w-lg',
'3xl': 'max-w-full md:max-w-2xl',
full: 'max-w-full md:max-w-full'
} as const

const Container = w.div('relative')
const GroupContainer = w.div('w-full max-w-full flex', {
variants: {
direction: (dir: 'col' | 'row') => dir === 'row' ? 'gap-2 flex-col md:items-center md:flex-row' : 'flex-col gap-1'
},
defaultVariants: {
direction: 'col'
}
})
const ButtonContainer = w.button('relative w-screen flex justify-between cursor-pointer rounded-md border border-gray-300 bg-white py-2 pl-3 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500', {
variants: {
size,
},
defaultVariants: {
size: 'md'
}
})
const ChevronIconContainer = w.span('pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400')
const OptionsContainer = w.ul('absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm', {
variants: {
size,
},
defaultVariants: {
size: 'md'
}
})

const OptionContainer = w.li('relative cursor-pointer select-none py-2 pl-3 pr-9', {
variants: {
hovered: (hovered: boolean) => hovered ? 'bg-gray-600 text-white' : 'text-gray-900'
}
})

const CheckIconContainer = w.span('absolute inset-y-0 right-0 flex items-center pr-3', {
variants: {
state: ({selected, hovered}: boolean) => selected && hovered ? 'text-white' : selected ? 'text-gray-600' : ''
}
})

export const Select = ({label, asRow, ExpendableIcon }) => {
const {
isOpen,
selectedItem,
getToggleButtonProps,
getLabelProps,
getMenuProps,
highlightedIndex,
getItemProps,
} = useSelect({
items: data,
itemToString,
})

return (
<Container>
<GroupContainer direction={asRow && 'row'}>
<Label label={label} {...getLabelProps()} />
<ButtonContainer
aria-label="toggle menu"
type="button"
{...getToggleButtonProps()}
>
<Text size='sm' weight='medium' className='truncate'>{selectedItem ? selectedItem.title : 'No option selected'}</Text>
<ChevronIconContainer aria-hidden="true">
{ExpendableIcon ? (<ExpendableIcon />) : <ChevronUpDownIcon className="h-5 w-5" aria-hidden="true" />}
</ChevronIconContainer>
</ButtonContainer>
</GroupContainer>
{isOpen ? <OptionsContainer {...getMenuProps()}>
{data.map((item, index) => (
<Option itemProps={{ ...getItemProps({ item, index }) }} item={item} selectedItem={selectedItem} highlightedIndex={highlightedIndex} index={index} />
))}
</OptionsContainer> : null}
</Container>
)
}

const Label = ({ label, ...props }) => label ? <Text size='sm' weight='medium' {...props} className='block min-w-max text-gray-700'>{label}</Text> : null
const Option = ({ item, selectedItem, itemProps, highlightedIndex, index }) => {
const selected = selectedItem?.id === item.id
const hovered = highlightedIndex === index
return (
<OptionContainer
key={item.id}
hovered={hovered}
{...itemProps}
>
<Text weight={selected ? 'semibold' : 'normal'} size='sm' className='block truncate'>
{item.title}
</Text>
{selected ? (
<CheckIconContainer state={{ selected, hovered }}>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</CheckIconContainer>
) : null}
</OptionContainer>
)
}

export default Select
@@ -15,6 +15,7 @@
"@statflo/textkit-widget-sdk-react": "^1.3.2",
"@tailwindcss/forms": "^0.5.2",
"core-js": "^3.6.5",
"downshift": "^6.1.10",
"gatsby": "4.20.0",
"gatsby-plugin-image": "2.20.0",
"gatsby-plugin-manifest": "4.20.0",
@@ -7844,6 +7844,11 @@ [email protected]^1.7.4:
safe-buffer "5.1.2"
vary "~1.1.2"

[email protected]^1.0.17:
version "1.0.17"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==

[email protected]:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@@ -9051,6 +9056,17 @@ [email protected]~10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==

[email protected]^6.1.10:
version "6.1.10"
resolved "https://registry.yarnpkg.com/downshift/-/downshift-6.1.10.tgz#65108dcac71eb7213b98232d8c110d1bde8b9e77"
integrity sha512-2ilyjks7ofrtYtAhL9QMzOwRzmLsNsckCs6MqybhfrCYtg8MIQVVamy38GEA86g7h6Vs25+LBYqpsjjoAvJh9Q==
dependencies:
"@babel/runtime" "^7.14.8"
compute-scroll-into-view "^1.0.17"
prop-types "^15.7.2"
react-is "^17.0.2"
tslib "^2.3.0"

[email protected]^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e"
@@ -17068,7 +17084,7 @@ [email protected]^5.1.0:
is-dom "^1.0.0"
prop-types "^15.0.0"

[email protected], [email protected]^17.0.1:
[email protected], [email protected]^17.0.1, [email protected]^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==

0 comments on commit 41d1db9

Please sign in to comment.