Skip to content

Commit

Permalink
Merge pull request #5 from functional-ui/option-group
Browse files Browse the repository at this point in the history
Option Group
  • Loading branch information
AlmogAdziashvili authored Jan 28, 2024
2 parents 00d3a02 + 954dd0a commit afc3b59
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

## [0.0.2] - 2023-12-21
Initial Release 🎉
5 changes: 5 additions & 0 deletions fui-option-group/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"main": "../dist/fui-option-group/index.js",
"module": "../dist/fui-option-group/index.mjs",
"types": "../dist/fui-option-group/index.d.ts"
}
43 changes: 43 additions & 0 deletions src/components/fui-option-group/fui-option-group.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.fui-option-group .fui-option-group-menu-option::after {
border-radius: var(--fui-radius-sm);
width: calc(100% + var(--fui-space-lg));
left: 50%;
transform: translateX(-50%);
}

.fui-option-group {
border-radius: var(--fui-radius-lg);
border: 1px solid var(--fui-color-divider-soft);
display: flex;
padding: var(--fui-space-lg) var(--fui-space-xlg);
flex-direction: column;
align-items: flex-start;
background: var(--fui-color-background-base);
}

.fui-option-group-menu-option {
display: flex;
height: 38px;
gap: var(--fui-space-sm);
align-items: center;
align-self: stretch;
font: var(--running-small-bold);
position: relative;
}

.fui-option-group-menu-group {
display: flex;
flex-direction: column;
min-height: 38px;
align-self: stretch;
margin-bottom: var(--fui-space-md);
}

.fui-option-group-menu-group:last-child {
margin-bottom: 0;
}

.fui-option-group-menu-group-label {
font: var(--running-small-bold);
color: var(--fui-color-foreground-softest);
}
64 changes: 64 additions & 0 deletions src/components/fui-option-group/fui-option-group.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import FuiIconPlaceholder16X16 from '../../icons/fui-icon-placeholder-16x16';
import { FuiOptionGroup } from './fui-option-group';

const meta = {
title: ' Components/OptionGroup',
component: FuiOptionGroup,
tags: ['autodocs'],
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/zHutj6e9DcPngHZTDtAL1u/Functional-UI-Kit?type=design&node-id=2574-19579&mode=design&t=jq0JgMhh6dwhuYIm-4'
}
},
argTypes: {
options: {
name: '🔗 options',
control: {
disable: true
}
},
className: {
control: {
disable: true
}
},
}
} satisfies Meta<typeof FuiOptionGroup>;

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

export const Default: Story = {
name: 'Basic',
args: {
options: [
{
label: 'Group Title', options: [
{ label: 'Option', value: '1' },
{ label: 'Option', value: '2' },
{ label: 'Option', value: '3' },
]
}
]
}
};

export const CustomOptions: Story = {
parameters: {
docs: {
description: {
story: 'You can customize your options by passing in a `prefix` (on single select) or `suffix` (on single or multi select).'
}
}
},
args: {
options: [
{ label: 'Option 1', value: '1', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> },
{ label: 'Option 2', value: '2', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> },
{ label: 'Option 3', value: '3', prefix: <FuiIconPlaceholder16X16 />, suffix: <FuiIconPlaceholder16X16 /> }
]
}
};
72 changes: 72 additions & 0 deletions src/components/fui-option-group/fui-option-group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { prefix } from '../prefix';
import classNames from 'classnames';

const compPrefix = `${prefix}-option-group`;

export interface Option {
label: string
value: string
prefix?: JSX.Element
suffix?: JSX.Element
}

export interface OptionGroup {
label: string
options: Option[]
}

export interface FuiOptionGroupProps {
options: (Option | OptionGroup)[]
className?: string
onSelect?: (value: string) => void
}

const isOptionGroup = (option: Option | OptionGroup): option is OptionGroup => {
return (option as OptionGroup).options !== undefined;
};

export const FuiOptionGroup = ({
options,
className,
onSelect,
}: FuiOptionGroupProps) => {
const classnames = classNames(compPrefix, className);

const renderMenuOption = (option: Option, index: number) => {
const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === ' ') {
e.preventDefault();
onSelect?.(option.value);
}
};

return (
<div tabIndex={0} onKeyDown={onKeyDown} key={index} className={`${compPrefix}-menu-option ${prefix}-interactable`} onClick={() => { onSelect?.(option.value); }}>
{option.prefix}
<div className={`${compPrefix}-menu-option-label`}>{option.label}</div>
{option.suffix}
</div>
);
};

const renderOptions = (options: (Option | OptionGroup)[]) => {
return options.map((option, index) => {
if (isOptionGroup(option)) {
return (
<div key={index} className={`${compPrefix}-menu-group`}>
<div className={`${compPrefix}-menu-group-label`}>{option.label}</div>
{option.options.map(renderMenuOption)}
</div>
);
}
return renderMenuOption(option, index);
});
};

return (
<div className={classnames}>
{renderOptions(options)}
</div>
);
};
1 change: 1 addition & 0 deletions src/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@import url('../components/fui-toggle/fui-toggle.css');
@import url('../components/fui-notification/fui-notification.css');
@import url('../components/fui-modal/fui-modal.css');
@import url('../components/fui-option-group/fui-option-group.css');

/* Box sizing rules */
[class*="fui"],
Expand Down
5 changes: 5 additions & 0 deletions src/stories/Introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Default as SwitchDefault } from '../components/fui-switch/fui-switch.st
import { Default as TextInputDefault } from '../components/fui-text-input/fui-text-input.stories.tsx';
import { Default as ToggleDefault } from '../components/fui-toggle/fui-toggle.stories.tsx';
import { Default as TooltipDefault } from '../components/fui-tooltip/fui-tooltip.stories.tsx';
import { Default as OptionGroupDefault } from '../components/fui-option-group/fui-option-group.stories.tsx';

<Meta title="Introduction" />

Expand Down Expand Up @@ -118,4 +119,8 @@ import { Default as TooltipDefault } from '../components/fui-tooltip/fui-tooltip
<Story of={NotificationDefault}></Story>
</div>
</div>
<div className='component-example-wrapper'>
<div className='component-example-label'>Option Group</div>
<Story of={OptionGroupDefault}></Story>
</div>
</div>
1 change: 1 addition & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export default defineConfig({
'fui-empty': resolve(__dirname, 'src/components/fui-empty/fui-empty.tsx'),
'fui-notification': resolve(__dirname, 'src/components/fui-notification/fui-notification.tsx'),
'fui-modal': resolve(__dirname, 'src/components/fui-modal/fui-modal.tsx'),
'fui-option-group': resolve(__dirname, 'src/components/fui-option-group/fui-option-group.tsx'),
},
fileName: '[name]/index',
formats: ['es', 'cjs'],
Expand Down

0 comments on commit afc3b59

Please sign in to comment.