Skip to content

Commit

Permalink
Merge pull request #21 from saitamau-maximum/feat/select-box
Browse files Browse the repository at this point in the history
feat: Select Box
  • Loading branch information
sor4chi authored Sep 23, 2023
2 parents 45659f0 + ee29255 commit 695b056
Show file tree
Hide file tree
Showing 7 changed files with 586 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"build:storybook": "storybook build"
},
"dependencies": {
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-tooltip": "^1.0.6",
"clsx": "^1.2.1",
"react": "^18.2.0"
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './card';
export * from './tooltip';
export * from './timeline';
export * from './input';
export * from './select-box';
export * from './header';
183 changes: 183 additions & 0 deletions packages/components/src/select-box/SelectBox.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
.trigger {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0.5rem 0.5rem 0.5rem 1rem;
gap: 0.5rem;
border-radius: 0.5rem;
border-width: 2px;
border-style: solid;
font-size: 1rem;
cursor: pointer;
transition: border-color 0.2s ease-in-out;

:global(.light) & {
border-color: $color-gray-200;
background-color: $color-gray-100;
color: $color-gray-900;

&:hover {
border-color: $color-gray-400;
}

&:focus {
outline: none;
}

&:focus-visible {
outline-offset: -2px;
border-color: $color-green-400;
}

&[data-placeholder] {
color: $color-gray-500;
}
}

:global(.dark) & {
border-color: $color-gray-700;
background-color: $color-gray-800;
color: $color-gray-50;

&:hover {
border-color: $color-gray-500;
}

&:focus {
outline: none;
}

&:focus-visible {
outline-offset: -2px;
border-color: $color-green-500;
}

&[data-placeholder] {
color: $color-gray-400;
}
}

&.expanded {
width: 100%;
}
}

.icon {
transition: transform 0.3s ease-in-out;
line-height: 0;

.trigger[data-state='open'] & {
transform: rotate(-180deg);
}
}

.content {
display: flex;
align-items: stretch;
justify-content: space-between;
width: 100%;
padding: 0.5rem;
box-sizing: border-box;
border-radius: 0.5rem;
transition: border-color 0.2s ease-in-out;

:global(.light) &,
&:global(.light) {
background-color: $color-gray-200;
box-shadow: $shadow-light;
}

:global(.dark) &,
&:global(.dark) {
background-color: $color-gray-700;
box-shadow: $shadow-dark;
}
}

.item {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.5rem 1rem;
box-sizing: border-box;
border-radius: 0.5rem;
transition: background-color 0.2s ease-in-out;
cursor: pointer;

:global(.light) & {
color: $color-gray-900;

&:hover {
outline: none;
background-color: $color-gray-100;
}

&:focus-visible {
outline-offset: -2px;
background-color: $color-gray-100;
}
}

:global(.dark) & {
color: $color-gray-50;

&:hover {
outline: none;
background-color: $color-gray-800;
}

&:focus-visible {
outline-offset: -2px;
background-color: $color-gray-800;
}
}
}

.groupLabel {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;

:global(.light) & {
color: $color-gray-500;
}

:global(.dark) & {
color: $color-gray-400;
}
}

.separator {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 2px;
margin: 0.5rem 0;
border-radius: 9999px;
transition: background-color 0.2s ease-in-out;

:global(.light) & {
background-color: $color-gray-300;
}

:global(.dark) & {
background-color: $color-gray-600;
}
}

.scrollButton {
display: flex;
align-items: center;
justify-content: center;
cursor: default;

:global(.light) & {
color: $color-gray-600;
}

:global(.dark) & {
color: $color-gray-300;
}
}
60 changes: 60 additions & 0 deletions packages/components/src/select-box/SelectBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Container } from '../__stories__/containter';
import { VStack } from '../__stories__/v-stack';

import { Props, SelectBox } from './SelectBox';

import type { Meta } from '@storybook/react';

const meta: Meta<typeof SelectBox> = {
component: SelectBox,
};

export default meta;

const SelectBoxes = (args: Props) => (
<Container>
<VStack theme="light">
<SelectBox {...args} _theme="light" />
</VStack>
<VStack theme="dark">
<SelectBox {...args} _theme="dark" />
</VStack>
</Container>
);

const DUMMY_OPTIONS = [
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option Option 2', value: 'option-2' },
{ label: 'Option Option Option 3', value: 'option-3' },
];

const DUMMY_GROUPED_OPTIONS = [
{
label: 'Group 1',
options: [
{ label: 'Option 1', value: 'option-1' },
{ label: 'Option Option 2', value: 'option-2' },
{ label: 'Option Option Option 3', value: 'option-3' },
],
},
{
label: 'Group 2',
options: [
{ label: 'Option 4', value: 'option-4' },
{ label: 'Option Option 5', value: 'option-5' },
{ label: 'Option Option Option 6', value: 'option-6' },
],
},
];

export const Simple = () => <SelectBoxes options={DUMMY_OPTIONS} />;

export const Grouped = () => (
<SelectBoxes groupedOptions={DUMMY_GROUPED_OPTIONS} />
);

export const Placeholder = () => (
<SelectBoxes options={DUMMY_OPTIONS} placeholder="Select an option" />
);

export const Expanded = () => <SelectBoxes expanded options={DUMMY_OPTIONS} />;
99 changes: 99 additions & 0 deletions packages/components/src/select-box/SelectBox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import * as Select from '@radix-ui/react-select';
import clsx from 'clsx';
import { ChevronDown, ChevronUp } from 'react-feather';

import styles from './SelectBox.module.scss';

type SingleGroupOptions = {
label: string;
value: string;
}[];

type GroupedOptions = {
label: string;
options: SingleGroupOptions;
}[];

export type Props = {
expanded?: boolean;
placeholder?: string;
/** don't use this prop, it's for storybook only */
_theme?: 'light' | 'dark';
} & (
| {
options: SingleGroupOptions;
groupedOptions?: undefined;
}
| {
options?: undefined;
groupedOptions: GroupedOptions;
}
);

const SelectBoxItems = ({ options }: { options: SingleGroupOptions }) => {
return options.map((option) => (
<Select.Item
key={option.value}
value={option.value}
className={styles.item}
>
<Select.ItemText>{option.label}</Select.ItemText>
</Select.Item>
));
};

const SelectBoxGroups = ({
groupedOptions,
}: {
groupedOptions: GroupedOptions;
}) => {
return groupedOptions.map((group, i) => (
<>
<Select.Group key={group.label}>
<Select.Label className={styles.groupLabel}>{group.label}</Select.Label>
<SelectBoxItems options={group.options} />
</Select.Group>
{i < groupedOptions.length - 1 && (
<Select.Separator className={styles.separator} />
)}
</>
));
};

export const SelectBox = ({
expanded = false,
options,
groupedOptions,
placeholder = 'Select an option',
_theme,
}: Props) => {
return (
<Select.Root>
<Select.Trigger
className={clsx(styles.trigger, expanded && styles.expanded)}
>
<Select.Value placeholder={placeholder} className={styles.value} />
<Select.Icon className={styles.icon}>
<ChevronDown />
</Select.Icon>
</Select.Trigger>

<Select.Portal>
<Select.Content className={clsx(_theme, styles.content)}>
<Select.ScrollUpButton className={styles.scrollButton}>
<ChevronUp />
</Select.ScrollUpButton>
<Select.Viewport>
{options && <SelectBoxItems options={options} />}
{groupedOptions && (
<SelectBoxGroups groupedOptions={groupedOptions} />
)}
</Select.Viewport>
<Select.ScrollDownButton className={styles.scrollButton}>
<ChevronDown />
</Select.ScrollDownButton>
</Select.Content>
</Select.Portal>
</Select.Root>
);
};
1 change: 1 addition & 0 deletions packages/components/src/select-box/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './SelectBox';
Loading

0 comments on commit 695b056

Please sign in to comment.