mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Added multiselect to AdminX Design System
refs. https://github.com/TryGhost/Team/issues/3318
This commit is contained in:
parent
e9ed7dfb64
commit
6900fd9eb0
4 changed files with 169 additions and 1 deletions
|
@ -76,6 +76,7 @@
|
|||
"postcss": "8.4.24",
|
||||
"postcss-import": "^15.1.0",
|
||||
"prop-types": "15.8.1",
|
||||
"react-select": "^5.7.3",
|
||||
"rollup-plugin-node-builtins": "2.1.2",
|
||||
"storybook": "7.0.18",
|
||||
"stylelint": "15.6.1",
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
import type {Meta, StoryObj} from '@storybook/react';
|
||||
|
||||
import MultiSelect, {MultiSelectOption} from './MultiSelect';
|
||||
import {MultiValue} from 'react-select';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Select / Multiselect',
|
||||
component: MultiSelect,
|
||||
tags: ['autodocs']
|
||||
} satisfies Meta<typeof MultiSelect>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof MultiSelect>;
|
||||
|
||||
const options = [
|
||||
{value: 'steph', label: 'Steph Curry'},
|
||||
{value: 'klay', label: 'Klay Thompson'},
|
||||
{value: 'dray', label: 'Draymond Green'}
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
options: options,
|
||||
placeholder: 'Select your players'
|
||||
}
|
||||
};
|
||||
|
||||
export const Clear: Story = {
|
||||
args: {
|
||||
options: options,
|
||||
clearBg: true
|
||||
}
|
||||
};
|
||||
|
||||
export const Black: Story = {
|
||||
args: {
|
||||
options: options,
|
||||
color: 'black'
|
||||
}
|
||||
};
|
||||
|
||||
export const WithTitle: Story = {
|
||||
args: {
|
||||
title: 'Choose your players',
|
||||
options: options,
|
||||
color: 'black'
|
||||
}
|
||||
};
|
||||
|
||||
export const WithTitleAndHint: Story = {
|
||||
args: {
|
||||
title: 'Choose your players',
|
||||
options: options,
|
||||
color: 'black',
|
||||
hint: 'I knew you\'d choose all'
|
||||
}
|
||||
};
|
||||
|
||||
export const WithDefaultValue: Story = {
|
||||
args: {
|
||||
title: 'Choose your players',
|
||||
options: options,
|
||||
color: 'black',
|
||||
hint: 'I knew you\'d choose all',
|
||||
defaultValues: [options[0]],
|
||||
onChange: (selected: MultiValue<MultiSelectOption>) => {
|
||||
selected?.map(o => (
|
||||
alert(`${o.label} (${o.value})`)
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
95
ghost/admin-x-settings/src/admin-x-ds/global/MultiSelect.tsx
Normal file
95
ghost/admin-x-settings/src/admin-x-ds/global/MultiSelect.tsx
Normal file
|
@ -0,0 +1,95 @@
|
|||
import Heading from './Heading';
|
||||
import Hint from './Hint';
|
||||
import React from 'react';
|
||||
import {MultiValue, default as ReactSelect, components} from 'react-select';
|
||||
|
||||
export type MultiSelectColor = 'grey' | 'black' | string;
|
||||
|
||||
export type MultiSelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface MultiSelectProps {
|
||||
options: MultiSelectOption[];
|
||||
defaultValues?: MultiSelectOption[];
|
||||
title?: string;
|
||||
clearBg?: boolean;
|
||||
error?: boolean;
|
||||
placeholder?: string;
|
||||
color?: MultiSelectColor
|
||||
hint?: string;
|
||||
onChange: (selected: MultiValue<MultiSelectOption>) => void
|
||||
}
|
||||
|
||||
const MultiSelect: React.FC<MultiSelectProps> = ({
|
||||
title = '',
|
||||
clearBg = false,
|
||||
error = false,
|
||||
placeholder,
|
||||
color = 'grey',
|
||||
hint = '',
|
||||
options,
|
||||
defaultValues,
|
||||
onChange,
|
||||
...props
|
||||
}) => {
|
||||
let multiValueColor;
|
||||
switch (color) {
|
||||
case 'black':
|
||||
multiValueColor = 'bg-black text-white';
|
||||
break;
|
||||
case 'grey':
|
||||
multiValueColor = 'bg-grey-300 text-black';
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const customClasses = {
|
||||
control: `w-full cursor-pointer appearance-none min-h-[40px] border-b ${!clearBg && 'bg-grey-75 px-[10px]'} py-2 outline-none ${error ? 'border-red' : 'border-grey-500 hover:border-grey-700'} ${title && 'mt-2'}`,
|
||||
valueContainer: 'gap-1',
|
||||
placeHolder: 'text-grey-600',
|
||||
menu: 'shadow py-2 rounded-b z-50 bg-white',
|
||||
option: 'hover:cursor-pointer hover:bg-grey-100 px-3 py-[6px]',
|
||||
multiValue: `rounded-sm items-center text-[14px] py-px pl-2 pr-1 gap-1.5 ${multiValueColor}`,
|
||||
noOptionsMessage: 'p-3 text-grey-600'
|
||||
};
|
||||
|
||||
const DropdownIndicator: React.FC<any> = ddiProps => (
|
||||
<components.DropdownIndicator {...ddiProps}>
|
||||
<div className={`absolute top-[14px] block h-2 w-2 rotate-45 border-[1px] border-l-0 border-t-0 border-grey-900 content-[''] ${clearBg ? 'right-0' : 'right-4'} `}></div>
|
||||
</components.DropdownIndicator>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
{title && <Heading useLabelTag={true}>{title}</Heading>}
|
||||
<ReactSelect
|
||||
classNames={{
|
||||
menuList: () => 'z-50',
|
||||
valueContainer: () => customClasses.valueContainer,
|
||||
control: () => customClasses.control,
|
||||
placeholder: () => customClasses.placeHolder,
|
||||
menu: () => customClasses.menu,
|
||||
option: () => customClasses.option,
|
||||
multiValue: () => customClasses.multiValue,
|
||||
noOptionsMessage: () => customClasses.noOptionsMessage
|
||||
}}
|
||||
components={{DropdownIndicator}}
|
||||
defaultValue={defaultValues}
|
||||
isClearable={false}
|
||||
options={options}
|
||||
placeholder={placeholder ? placeholder : ''}
|
||||
isMulti
|
||||
unstyled
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
/>
|
||||
{hint && <Hint color={error ? 'red' : ''}>{hint}</Hint>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MultiSelect;
|
|
@ -4,7 +4,7 @@ import Select from './Select';
|
|||
import {SelectOption} from './Select';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Select',
|
||||
title: 'Global / Select / Simple select',
|
||||
component: Select,
|
||||
tags: ['autodocs'],
|
||||
decorators: [(_story: any) => (<div style={{maxWidth: '400px'}}>{_story()}</div>)],
|
||||
|
|
Loading…
Add table
Reference in a new issue