mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 21:37:59 +00:00
feat(ui) added select, spinner components
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SelectProps } from '@radix-ui/react-select';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Select, SelectItem } from './Select';
|
||||
|
||||
const meta: Meta<typeof Select> = {
|
||||
title: 'Components/Select',
|
||||
component: Select,
|
||||
tags: ['v2'],
|
||||
argTypes: {
|
||||
placeholder: {
|
||||
defaultValue: 'Type something...'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Select>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select placeholder="Type anything..." className="w-72" {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
const Controlled = (args: SelectProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selected, setSelected] = useState('');
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Select
|
||||
defaultValue="1"
|
||||
className="w-72"
|
||||
open={isOpen}
|
||||
onValueChange={(val) => setSelected(val)}
|
||||
value={selected}
|
||||
onOpenChange={(open) => setIsOpen(open)}
|
||||
{...args}
|
||||
>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Control: Story = {
|
||||
render: (args) => <Controlled {...args} />
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select defaultValue="1" className="w-72" {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2" isDisabled>
|
||||
Peter
|
||||
</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
render: (args) => (
|
||||
<div className="">
|
||||
<Select defaultValue="1" className="w-72" isLoading {...args}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
const AsyncSelectOptions = () => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new Promise<void>((resolve): void => {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
resolve();
|
||||
}, 1000);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="">
|
||||
<Select placeholder="Hello" className="w-72" isLoading={isLoading}>
|
||||
<SelectItem value="1">John</SelectItem>
|
||||
<SelectItem value="2">Peter</SelectItem>
|
||||
<SelectItem value="3">Suzy</SelectItem>
|
||||
</Select>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Async: Story = {
|
||||
render: (args) => <AsyncSelectOptions {...args} />
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { forwardRef, ReactNode } from 'react';
|
||||
import { faCheck, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
import { Spinner } from '../Spinner';
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
placeholder?: string;
|
||||
className?: string;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
type SelectProps = SelectPrimitive.SelectProps & Props;
|
||||
|
||||
export const Select = forwardRef<HTMLButtonElement, SelectProps>(
|
||||
({ children, placeholder, className, isLoading, ...props }, ref): JSX.Element => {
|
||||
return (
|
||||
<SelectPrimitive.Root {...props}>
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={twMerge(
|
||||
`select inline-flex items-center justify-between
|
||||
px-4 py-2.5 text-sm text-white rounded-md bg-mineshaft-800`,
|
||||
className
|
||||
)}
|
||||
>
|
||||
<SelectPrimitive.Value placeholder={placeholder} />
|
||||
<SelectPrimitive.Icon className="ml-3">
|
||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
position="popper"
|
||||
sideOffset={5}
|
||||
className="overflow-hidden text-white rounded-md shadow-md bg-mineshaft-800"
|
||||
style={{ width: 'var(--radix-select-trigger-width)' }}
|
||||
>
|
||||
<SelectPrimitive.ScrollUpButton>
|
||||
<FontAwesomeIcon icon={faChevronUp} size="sm" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
<SelectPrimitive.Viewport className="p-2">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center">
|
||||
<Spinner size="xs" />
|
||||
<span className="ml-2 text-xs text-gray-500">Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectPrimitive.ScrollDownButton>
|
||||
<FontAwesomeIcon icon={faChevronDown} size="sm" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
</SelectPrimitive.Root>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Select.displayName = 'Select';
|
||||
|
||||
type SelectItemProps = Omit<SelectPrimitive.SelectItemProps, 'disabled'> & {
|
||||
isDisabled?: boolean;
|
||||
isSelected?: boolean;
|
||||
};
|
||||
|
||||
export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
|
||||
({ children, className, isSelected, isDisabled, ...props }, forwardedRef) => {
|
||||
return (
|
||||
<SelectPrimitive.Item
|
||||
{...props}
|
||||
className={twMerge(
|
||||
`text-sm rounded-sm transition-all hover:text-primary
|
||||
hover:bg-mineshaft-700 flex items-center pl-10 pr-4 py-2 cursor-pointer
|
||||
select-none outline-none relative`,
|
||||
isSelected && 'text-primary',
|
||||
isDisabled && 'text-gray-600 hover:bg-transparent cursor-not-allowed hover:text-gray-600',
|
||||
className
|
||||
)}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<SelectPrimitive.ItemIndicator className="absolute left-2">
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
<SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SelectItem.displayName = 'SelectItem';
|
||||
@@ -0,0 +1 @@
|
||||
export {Select} from './Select'
|
||||
Reference in New Issue
Block a user