Skip to content

Commit 954dd0a

Browse files
option-group #draftpr
1 parent 5556d4c commit 954dd0a

File tree

8 files changed

+192
-0
lines changed

8 files changed

+192
-0
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99
* [FuiPopover] - Removed `react-tiny-popover` and replace with `floating-ui`
10+
* [FuiOptionGroup] - New Component 🎉
1011

1112
## [0.0.2] - 2023-12-21
1213
Initial Release 🎉

fui-option-group/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"main": "../dist/fui-option-group/index.js",
3+
"module": "../dist/fui-option-group/index.mjs",
4+
"types": "../dist/fui-option-group/index.d.ts"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.fui-option-group .fui-option-group-menu-option::after {
2+
border-radius: var(--fui-radius-sm);
3+
width: calc(100% + var(--fui-space-lg));
4+
left: 50%;
5+
transform: translateX(-50%);
6+
}
7+
8+
.fui-option-group {
9+
border-radius: var(--fui-radius-lg);
10+
border: 1px solid var(--fui-color-divider-soft);
11+
display: flex;
12+
padding: var(--fui-space-lg) var(--fui-space-xlg);
13+
flex-direction: column;
14+
align-items: flex-start;
15+
background: var(--fui-color-background-base);
16+
}
17+
18+
.fui-option-group-menu-option {
19+
display: flex;
20+
height: 38px;
21+
gap: var(--fui-space-sm);
22+
align-items: center;
23+
align-self: stretch;
24+
font: var(--running-small-bold);
25+
position: relative;
26+
}
27+
28+
.fui-option-group-menu-group {
29+
display: flex;
30+
flex-direction: column;
31+
min-height: 38px;
32+
align-self: stretch;
33+
margin-bottom: var(--fui-space-md);
34+
}
35+
36+
.fui-option-group-menu-group:last-child {
37+
margin-bottom: 0;
38+
}
39+
40+
.fui-option-group-menu-group-label {
41+
font: var(--running-small-bold);
42+
color: var(--fui-color-foreground-softest);
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import type { Meta, StoryObj } from '@storybook/react';
3+
import FuiIconPlaceholder16X16 from '../../icons/fui-icon-placeholder-16x16';
4+
import { FuiOptionGroup } from './fui-option-group';
5+
6+
const meta = {
7+
title: ' Components/OptionGroup',
8+
component: FuiOptionGroup,
9+
tags: ['autodocs'],
10+
parameters: {
11+
design: {
12+
type: 'figma',
13+
url: 'https://www.figma.com/file/zHutj6e9DcPngHZTDtAL1u/Functional-UI-Kit?type=design&node-id=2574-19579&mode=design&t=jq0JgMhh6dwhuYIm-4'
14+
}
15+
},
16+
argTypes: {
17+
options: {
18+
name: '🔗 options',
19+
control: {
20+
disable: true
21+
}
22+
},
23+
className: {
24+
control: {
25+
disable: true
26+
}
27+
},
28+
}
29+
} satisfies Meta<typeof FuiOptionGroup>;
30+
31+
export default meta;
32+
type Story = StoryObj<typeof meta>;
33+
34+
export const Default: Story = {
35+
name: 'Basic',
36+
args: {
37+
options: [
38+
{
39+
label: 'Group Title', options: [
40+
{ label: 'Option', value: '1' },
41+
{ label: 'Option', value: '2' },
42+
{ label: 'Option', value: '3' },
43+
]
44+
}
45+
]
46+
}
47+
};
48+
49+
export const CustomOptions: Story = {
50+
parameters: {
51+
docs: {
52+
description: {
53+
story: 'You can customize your options by passing in a `prefix` (on single select) or `suffix` (on single or multi select).'
54+
}
55+
}
56+
},
57+
args: {
58+
options: [
59+
{ label: 'Option 1', value: '1', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> },
60+
{ label: 'Option 2', value: '2', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> },
61+
{ label: 'Option 3', value: '3', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> }
62+
]
63+
}
64+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React from 'react';
2+
import { prefix } from '../prefix';
3+
import classNames from 'classnames';
4+
5+
const compPrefix = `${prefix}-option-group`;
6+
7+
export interface Option {
8+
label: string
9+
value: string
10+
prefix?: JSX.Element
11+
suffix?: JSX.Element
12+
}
13+
14+
export interface OptionGroup {
15+
label: string
16+
options: Option[]
17+
}
18+
19+
export interface FuiOptionGroupProps {
20+
options: (Option | OptionGroup)[]
21+
className?: string
22+
onSelect?: (value: string) => void
23+
}
24+
25+
const isOptionGroup = (option: Option | OptionGroup): option is OptionGroup => {
26+
return (option as OptionGroup).options !== undefined;
27+
};
28+
29+
export const FuiOptionGroup = ({
30+
options,
31+
className,
32+
onSelect,
33+
}: FuiOptionGroupProps) => {
34+
const classnames = classNames(compPrefix, className);
35+
36+
const renderMenuOption = (option: Option, index: number) => {
37+
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
38+
if (e.key === ' ') {
39+
e.preventDefault();
40+
onSelect?.(option.value);
41+
}
42+
};
43+
44+
return (
45+
<div tabIndex={0} onKeyDown={onKeyDown} key={index} className={`${compPrefix}-menu-option ${prefix}-interactable`} onClick={() => { onSelect?.(option.value); }}>
46+
{option.prefix}
47+
<div className={`${compPrefix}-menu-option-label`}>{option.label}</div>
48+
{option.suffix}
49+
</div>
50+
);
51+
};
52+
53+
const renderOptions = (options: (Option | OptionGroup)[]) => {
54+
return options.map((option, index) => {
55+
if (isOptionGroup(option)) {
56+
return (
57+
<div key={index} className={`${compPrefix}-menu-group`}>
58+
<div className={`${compPrefix}-menu-group-label`}>{option.label}</div>
59+
{option.options.map(renderMenuOption)}
60+
</div>
61+
);
62+
}
63+
return renderMenuOption(option, index);
64+
});
65+
};
66+
67+
return (
68+
<div className={classnames}>
69+
{renderOptions(options)}
70+
</div>
71+
);
72+
};

src/css/main.css

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
@import url('../components/fui-toggle/fui-toggle.css');
2525
@import url('../components/fui-notification/fui-notification.css');
2626
@import url('../components/fui-modal/fui-modal.css');
27+
@import url('../components/fui-option-group/fui-option-group.css');
2728

2829
/* Box sizing rules */
2930
[class*="fui"],

src/stories/Introduction.mdx

+5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Default as SwitchDefault } from '../components/fui-switch/fui-switch.st
1414
import { Default as TextInputDefault } from '../components/fui-text-input/fui-text-input.stories.tsx';
1515
import { Default as ToggleDefault } from '../components/fui-toggle/fui-toggle.stories.tsx';
1616
import { Default as TooltipDefault } from '../components/fui-tooltip/fui-tooltip.stories.tsx';
17+
import { Default as OptionGroupDefault } from '../components/fui-option-group/fui-option-group.stories.tsx';
1718

1819
<Meta title="Introduction" />
1920

@@ -118,4 +119,8 @@ import { Default as TooltipDefault } from '../components/fui-tooltip/fui-tooltip
118119
<Story of={NotificationDefault}></Story>
119120
</div>
120121
</div>
122+
<div className='component-example-wrapper'>
123+
<div className='component-example-label'>Option Group</div>
124+
<Story of={OptionGroupDefault}></Story>
125+
</div>
121126
</div>

vite.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export default defineConfig({
2222
'fui-empty': resolve(__dirname, 'src/components/fui-empty/fui-empty.tsx'),
2323
'fui-notification': resolve(__dirname, 'src/components/fui-notification/fui-notification.tsx'),
2424
'fui-modal': resolve(__dirname, 'src/components/fui-modal/fui-modal.tsx'),
25+
'fui-option-group': resolve(__dirname, 'src/components/fui-option-group/fui-option-group.tsx'),
2526
},
2627
fileName: '[name]/index',
2728
formats: ['es', 'cjs'],

0 commit comments

Comments
 (0)