Skip to content

Commit

Permalink
Merge pull request #8 from broadlume/combobox
Browse files Browse the repository at this point in the history
Adds Combobox
  • Loading branch information
dreadhalor authored Jan 29, 2024
2 parents 3b6e218 + 9528aeb commit e909696
Show file tree
Hide file tree
Showing 20 changed files with 5,230 additions and 2,612 deletions.
1 change: 1 addition & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const config: StorybookConfig = {
'@storybook/addon-interactions',
'@storybook/addon-styling',
'@storybook/addon-a11y',
'@storybook/addon-mdx-gfm'
],
framework: {
name: '@storybook/react-vite',
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"stylelint.enable": true,
"stylelint.validate": ["css", "scss"],
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": true
"source.fixAll.stylelint": "explicit"
},
"scss.validate": false,
"css.validate": false,
Expand Down
6,640 changes: 4,062 additions & 2,578 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 21 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,25 @@
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
"react": "^18.0",
"react-dom": "^18.0"
},
"devDependencies": {
"@storybook/addon-a11y": "^7.0.26",
"@storybook/addon-essentials": "^7.0.26",
"@storybook/addon-interactions": "^7.0.26",
"@storybook/addon-links": "^7.0.26",
"@storybook/addon-a11y": "^7.6.10",
"@storybook/addon-essentials": "^7.6.10",
"@storybook/addon-interactions": "^7.6.10",
"@storybook/addon-links": "^7.6.10",
"@storybook/addon-mdx-gfm": "^7.6.10",
"@storybook/addon-styling": "^1.3.2",
"@storybook/blocks": "^7.0.26",
"@storybook/manager-api": "^7.0.26",
"@storybook/react": "^7.1.0-alpha.31",
"@storybook/react-vite": "^7.0.26",
"@storybook/blocks": "^7.6.10",
"@storybook/manager-api": "^7.6.10",
"@storybook/react": "^7.6.10",
"@storybook/react-vite": "^7.6.10",
"@storybook/testing-library": "^0.2.0",
"@storybook/theming": "^7.0.26",
"@storybook/theming": "^7.6.10",
"@tailwindcss/container-queries": "^0.1.1",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.1",
"@types/react": "^18.0",
"@types/react-dom": "^18.0",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@typescript-eslint/parser": "^5.59.11",
"@vitejs/plugin-react": "^4.0.0",
Expand All @@ -76,10 +77,10 @@
"prettier": "^2.8.8",
"prettier-plugin-tailwindcss": "^0.3.0",
"prop-types": "^15.8.1",
"react": "^16.8.1",
"react-dom": "^16.8.1",
"react": "^18.0",
"react-dom": "^18.0",
"sass": "^1.63.3",
"storybook": "^7.0.26",
"storybook": "^7.6.10",
"stylelint": "^15.7.0",
"stylelint-config-standard": "^33.0.0",
"stylelint-config-standard-scss": "^9.0.0",
Expand All @@ -95,7 +96,7 @@
"@radix-ui/react-alert-dialog": "^1.0.4",
"@radix-ui/react-avatar": "^1.0.3",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
Expand All @@ -110,13 +111,16 @@
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-tooltip": "^1.0.6",
"carloslfu-cmdk-internal": "^0.3.1",
"class-variance-authority": "^0.6.1",
"clsx": "^1.2.1",
"date-fns": "^2.30.0",
"lucide-react": "^0.245.0",
"react-data-table-component": "^7.5.4",
"react-day-picker": "^8.8.0",
"react-icons": "^4.10.1",
"react-resizable-panels": "^1.0.9",
"react-resize-detector": "^10.0.1",
"recharts": "^2.7.3",
"styled-components": "^6.1.1",
"tailwind-merge": "^1.13.2",
Expand Down
236 changes: 236 additions & 0 deletions src/components/combobox/combobox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { Meta, StoryObj } from '@storybook/react';
import {
Combobox,
ComboboxContent,
ComboboxEmpty,
ComboboxFooter,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxList,
ComboboxValue,
} from './combobox';
import {
Button,
Checkbox,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
Input,
Label,
} from '@src/index';
import { FaPlus } from 'react-icons/fa';
import { useState } from 'react';

const meta: Meta<typeof Combobox> = {
component: Combobox,
title: 'Components/Combobox',
};

export default meta;
type Story = StoryObj<typeof Combobox>;

const frameworks = [
{
value: 'next.js',
label: 'Next.js',
},
{
value: 'sveltekit',
label: 'SvelteKit',
},
{
value: 'nuxt.js',
label: 'Nuxt.js',
},
{
value: 'remix',
label: 'Remix',
},
{
value: 'astro',
label: 'Astro',
},
];

const locations = [
{
value: 'clearwater',
label: 'Clearwater',
},
{
value: 'north-tampa',
label: 'North Tampa',
},
{
value: 'safety-harbor',
label: 'Safety Harbor',
},
{
value: 'south-tampa',
label: 'South Tampa',
},
{
value: 'st-petersburg',
label: 'St. Petersburg',
},
];

// A long list of any non-repeating values, for testing scrolling.
const longList = Array.from({ length: 100 }, (_, i) => ({
value: i.toString(),
label: `Item ${i}`,
}));

export const Demo: Story = {
render: (_) => (
<Combobox>
<ComboboxValue className='~w-[200px]' placeholder='Select framework...' />
<ComboboxContent>
<ComboboxInput placeholder='Search frameworks...' />
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
<ComboboxGroup>
{frameworks.map((framework) => (
<ComboboxItem key={framework.value} value={framework.value}>
{framework.label}
</ComboboxItem>
))}
</ComboboxGroup>
</ComboboxList>
</ComboboxContent>
</Combobox>
),
};

const LocationDemoComponent = ({ placeholder, values }: any) => {
const [open, setOpen] = useState(false);

return (
<Combobox>
<ComboboxValue className='~w-[300px]' placeholder={placeholder} />
<ComboboxContent>
<ComboboxInput placeholder='Search locations...' />
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
<ComboboxGroup>
{values.map((location) => (
<ComboboxItem key={location.value} value={location.value}>
{location.label}
</ComboboxItem>
))}
</ComboboxGroup>
</ComboboxList>
<Dialog open={open} onOpenChange={setOpen}>
<ComboboxFooter onSelect={() => setOpen((prev) => !prev)}>
<FaPlus />
Add location
</ComboboxFooter>
<DialogContent className='sm:~max-w-[425px]'>
<DialogHeader>
<DialogTitle>Add location</DialogTitle>
<DialogDescription>
Add a new location to your list.
</DialogDescription>
</DialogHeader>
<div className='~grid ~grid-cols-4 ~items-center ~gap-4'>
<Label htmlFor='name' className='~text-right'>
Name
</Label>
<Input id='name' defaultValue='' className='~col-span-3' />
</div>
<DialogFooter>
<Button variant='secondary' onClick={() => setOpen(false)}>
Cancel
</Button>
<Button type='submit' onClick={() => setOpen(false)}>
Add
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ComboboxContent>
</Combobox>
);
};

export const FooterDemo: StoryObj = {
render: LocationDemoComponent,
args: {
placeholder: 'Location',
values: locations,
},
};

const ControlledDemoComponent = ({ placeholder, values }: any) => {
const [value, setValue] = useState<string[]>([]);
return (
<div className='tw-reset ~flex ~flex-col ~gap-2'>
<Combobox value={value} onChange={setValue}>
<ComboboxValue className='~w-[300px]' placeholder={placeholder} />
<ComboboxContent>
<ComboboxInput placeholder={placeholder} />
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
<ComboboxGroup>
{values.map((framework) => (
<ComboboxItem key={framework.value} value={framework.value}>
{framework.label}
</ComboboxItem>
))}
</ComboboxGroup>
</ComboboxList>
</ComboboxContent>
</Combobox>
<p>External control:</p>
{values.map((_value) => (
<div className='~flex ~gap-1' key={_value.value}>
<Checkbox
id={_value.value}
checked={value.includes(_value.value)}
onCheckedChange={(e) => {
if (e === true) {
setValue((prev) => [...prev, _value.value]);
} else if (e === false) {
setValue((prev) => prev.filter((v) => v !== _value.value));
}
}}
/>
<Label htmlFor={_value.value}>{_value.label}</Label>
</div>
))}
</div>
);
};
export const Controlled: StoryObj = {
render: ControlledDemoComponent,
args: {
placeholder: 'Select framework...',
values: frameworks,
},
};

export const LongListDemo: Story = {
render: (_) => (
<Combobox>
<ComboboxValue className='~w-[200px]' placeholder='Select...' />
<ComboboxContent>
<ComboboxInput placeholder='Random placeholder' />
<ComboboxEmpty>No results found.</ComboboxEmpty>
<ComboboxList>
<ComboboxGroup>
{longList.map((item) => (
<ComboboxItem key={item.value} value={item.value}>
{item.label}
</ComboboxItem>
))}
</ComboboxGroup>
</ComboboxList>
<ComboboxFooter>Test</ComboboxFooter>
</ComboboxContent>
</Combobox>
),
};
Loading

0 comments on commit e909696

Please sign in to comment.