mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-14 10:04:03 +00:00
Merge remote-tracking branch 'origin/refactor-dropdown' into spacedrive-but-themable
This commit is contained in:
commit
90a267930a
|
@ -1,9 +1,7 @@
|
||||||
import { Disclosure, Transition } from '@headlessui/react';
|
import { ChevronRightIcon } from '@heroicons/react/24/solid';
|
||||||
import { ChevronRightIcon, XMarkIcon } from '@heroicons/react/24/solid';
|
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import clsx from 'clsx';
|
|
||||||
import { List, X } from 'phosphor-react';
|
import { List, X } from 'phosphor-react';
|
||||||
import { PropsWithChildren, useEffect, useState } from 'react';
|
import { PropsWithChildren, useState } from 'react';
|
||||||
import pkg from 'react-burger-menu';
|
import pkg from 'react-burger-menu';
|
||||||
|
|
||||||
import { Doc, DocsNavigation, toTitleCase } from '../pages/docs/api';
|
import { Doc, DocsNavigation, toTitleCase } from '../pages/docs/api';
|
||||||
|
@ -32,9 +30,10 @@ export default function DocsLayout(props: Props) {
|
||||||
<div className="visible h-screen pb-20 overflow-x-hidden custom-scroll doc-sidebar-scroll bg-gray-650 pt-7 px-7 sm:invisible">
|
<div className="visible h-screen pb-20 overflow-x-hidden custom-scroll doc-sidebar-scroll bg-gray-650 pt-7 px-7 sm:invisible">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
onClick={() => setMenuOpen(!menuOpen)}
|
||||||
icon={<X weight="bold" className="w-6 h-6" />}
|
|
||||||
className="!px-1 -ml-0.5 mb-3 !border-none"
|
className="!px-1 -ml-0.5 mb-3 !border-none"
|
||||||
/>
|
>
|
||||||
|
<X weight="bold" className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
<DocsSidebar activePath={props?.doc?.url} navigation={props.navigation} />
|
<DocsSidebar activePath={props?.doc?.url} navigation={props.navigation} />
|
||||||
</div>
|
</div>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
@ -45,11 +44,9 @@ export default function DocsLayout(props: Props) {
|
||||||
<div className="flex flex-col w-full sm:flex-row" id="page-container">
|
<div className="flex flex-col w-full sm:flex-row" id="page-container">
|
||||||
<div className="h-12 px-5 flex w-full border-t border-gray-600 border-b mt-[65px] sm:hidden items-center ">
|
<div className="h-12 px-5 flex w-full border-t border-gray-600 border-b mt-[65px] sm:hidden items-center ">
|
||||||
<div className="flex sm:hidden">
|
<div className="flex sm:hidden">
|
||||||
<Button
|
<Button onClick={() => setMenuOpen(!menuOpen)} className="!px-2 ml-1 !border-none">
|
||||||
onClick={() => setMenuOpen(!menuOpen)}
|
<List weight="bold" className="w-6 h-6" />
|
||||||
icon={<List weight="bold" className="w-6 h-6" />}
|
</Button>
|
||||||
className="!px-2 ml-1 !border-none"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{props.doc?.url.split('/').map((item, index) => {
|
{props.doc?.url.split('/').map((item, index) => {
|
||||||
if (index === 2) return null;
|
if (index === 2) return null;
|
||||||
|
|
|
@ -9,11 +9,9 @@ import { Discord, Github } from '@icons-pack/react-simple-icons';
|
||||||
import AppLogo from '@sd/assets/images/logo.png';
|
import AppLogo from '@sd/assets/images/logo.png';
|
||||||
import { Dropdown, DropdownItem } from '@sd/ui';
|
import { Dropdown, DropdownItem } from '@sd/ui';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
import { DotsThreeVertical } from 'phosphor-react';
|
import { DotsThreeVertical } from 'phosphor-react';
|
||||||
import { PropsWithChildren, useEffect, useState } from 'react';
|
import { PropsWithChildren, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
|
||||||
import { positions } from '../pages/careers.page';
|
import { positions } from '../pages/careers.page';
|
||||||
import { getWindow } from '../utils';
|
import { getWindow } from '../utils';
|
||||||
|
|
||||||
|
@ -96,7 +94,7 @@ export default function NavBar() {
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Dropdown
|
<Dropdown.Root
|
||||||
className="absolute block h-6 text-white w-44 top-2 right-4 lg:hidden"
|
className="absolute block h-6 text-white w-44 top-2 right-4 lg:hidden"
|
||||||
itemsClassName="!rounded-2xl shadow-2xl shadow-black p-2 !bg-gray-850 mt-2 !border-gray-500"
|
itemsClassName="!rounded-2xl shadow-2xl shadow-black p-2 !bg-gray-850 mt-2 !border-gray-500"
|
||||||
itemButtonClassName="!py-1 !rounded-md text-[15px]"
|
itemButtonClassName="!py-1 !rounded-md text-[15px]"
|
||||||
|
|
|
@ -3,9 +3,10 @@ import { PlusIcon } from '@heroicons/react/24/solid';
|
||||||
import { useCurrentLibrary, useLibraryMutation, useLibraryQuery, usePlatform } from '@sd/client';
|
import { useCurrentLibrary, useLibraryMutation, useLibraryQuery, usePlatform } from '@sd/client';
|
||||||
import { LocationCreateArgs } from '@sd/client';
|
import { LocationCreateArgs } from '@sd/client';
|
||||||
import { Button, CategoryHeading, Dropdown, OverlayPanel } from '@sd/ui';
|
import { Button, CategoryHeading, Dropdown, OverlayPanel } from '@sd/ui';
|
||||||
|
import { restyle } from '@sd/ui';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { CheckCircle, CirclesFour, Planet, WaveTriangle } from 'phosphor-react';
|
import { CheckCircle, CirclesFour, Planet, WaveTriangle } from 'phosphor-react';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren, forwardRef } from 'react';
|
||||||
import { NavLink, NavLinkProps, useNavigate } from 'react-router-dom';
|
import { NavLink, NavLinkProps, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
import { useOperatingSystem } from '../../hooks/useOperatingSystem';
|
||||||
|
@ -157,6 +158,8 @@ export function Sidebar() {
|
||||||
const os = useOperatingSystem();
|
const os = useOperatingSystem();
|
||||||
const { library, libraries, isLoading: isLoadingLibraries, switchLibrary } = useCurrentLibrary();
|
const { library, libraries, isLoading: isLoadingLibraries, switchLibrary } = useCurrentLibrary();
|
||||||
|
|
||||||
|
const itemStyles = macOnly(os, 'dark:hover:bg-gray-550 dark:hover:bg-opacity-50');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
@ -166,52 +169,59 @@ export function Sidebar() {
|
||||||
>
|
>
|
||||||
<WindowControls />
|
<WindowControls />
|
||||||
|
|
||||||
<Dropdown
|
<Dropdown.Root
|
||||||
buttonProps={{
|
className="mt-2"
|
||||||
justify: 'left',
|
button={
|
||||||
className: clsx(
|
<Dropdown.Button
|
||||||
`flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded !bg-gray-50 border-gray-150 hover:!bg-gray-1000 dark:!bg-gray-500 dark:hover:!bg-gray-500 dark:!border-gray-550 dark:hover:!border-gray-500`,
|
variant="gray"
|
||||||
macOnly(
|
className={clsx(
|
||||||
os,
|
`flex w-full text-left max-w-full mb-1 mt-1 -mr-0.5 shadow-xs rounded !bg-gray-50 border-gray-150 hover:!bg-gray-1000 dark:!bg-gray-500 dark:hover:!bg-gray-500 dark:!border-gray-550 dark:hover:!border-gray-500`,
|
||||||
'dark:!bg-opacity-40 dark:hover:!bg-opacity-70 dark:!border-[#333949] dark:hover:!border-[#394052]'
|
(library === null || isLoadingLibraries) && 'text-gray-300',
|
||||||
)
|
macOnly(
|
||||||
),
|
os,
|
||||||
variant: 'gray'
|
'dark:!bg-opacity-40 dark:hover:!bg-opacity-70 dark:!border-[#333949] dark:hover:!border-[#394052]'
|
||||||
}}
|
)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{/* this shouldn't default to "My Library", it is only this way for landing demo */}
|
||||||
|
<span className="w-32 truncate">
|
||||||
|
{isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
|
||||||
|
</span>
|
||||||
|
</Dropdown.Button>
|
||||||
|
}
|
||||||
// to support the transparent sidebar on macOS we use slightly adjusted styles
|
// to support the transparent sidebar on macOS we use slightly adjusted styles
|
||||||
itemsClassName={macOnly(os, 'dark:bg-gray-800 dark:divide-gray-600')}
|
itemsClassName={macOnly(os, 'dark:bg-gray-800 dark:divide-gray-600')}
|
||||||
itemButtonClassName={macOnly(os, 'dark:hover:bg-gray-550 dark:hover:bg-opacity-50')}
|
>
|
||||||
// this shouldn't default to "My Library", it is only this way for landing demo
|
<Dropdown.Section>
|
||||||
buttonText={isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
|
{libraries?.map((lib) => (
|
||||||
buttonTextClassName={library === null || isLoadingLibraries ? 'text-gray-300' : undefined}
|
<Dropdown.Item
|
||||||
items={[
|
className={itemStyles}
|
||||||
libraries?.map((lib) => ({
|
selected={lib.uuid === library?.uuid}
|
||||||
name: lib.config.name,
|
key={lib.uuid}
|
||||||
selected: lib.uuid === library?.uuid,
|
onClick={() => switchLibrary(lib.uuid)}
|
||||||
onPress: () => switchLibrary(lib.uuid)
|
>
|
||||||
})) || [],
|
{lib.config.name}
|
||||||
[
|
</Dropdown.Item>
|
||||||
{
|
))}
|
||||||
name: 'Library Settings',
|
</Dropdown.Section>
|
||||||
icon: CogIcon,
|
<Dropdown.Section>
|
||||||
to: 'settings/library'
|
<Dropdown.Item className={itemStyles} icon={CogIcon} to="settings/library">
|
||||||
},
|
Library Settings
|
||||||
{
|
</Dropdown.Item>
|
||||||
name: 'Add Library',
|
<CreateLibraryDialog>
|
||||||
icon: PlusIcon,
|
<Dropdown.Item className={itemStyles} icon={PlusIcon}>
|
||||||
wrapItemComponent: CreateLibraryDialog
|
Add Library
|
||||||
},
|
</Dropdown.Item>
|
||||||
{
|
</CreateLibraryDialog>
|
||||||
name: 'Lock',
|
<Dropdown.Item
|
||||||
icon: LockClosedIcon,
|
className={itemStyles}
|
||||||
disabled: true,
|
icon={LockClosedIcon}
|
||||||
onPress: () => {
|
onClick={() => alert('TODO: Not implemented yet!')}
|
||||||
alert('TODO: Not implemented yet!');
|
>
|
||||||
}
|
Lock
|
||||||
}
|
</Dropdown.Item>
|
||||||
]
|
</Dropdown.Section>
|
||||||
]}
|
</Dropdown.Root>
|
||||||
/>
|
|
||||||
<div className="pt-1">
|
<div className="pt-1">
|
||||||
<SidebarLink to="/overview">
|
<SidebarLink to="/overview">
|
||||||
<Icon component={Planet} />
|
<Icon component={Planet} />
|
||||||
|
|
|
@ -171,11 +171,8 @@ export default function OverviewScreen() {
|
||||||
// ctaAction={() => {}}
|
// ctaAction={() => {}}
|
||||||
ctaLabel="Connect"
|
ctaLabel="Connect"
|
||||||
trigger={
|
trigger={
|
||||||
<Button
|
<Button size="sm" variant="gray">
|
||||||
size="sm"
|
<PlusIcon className="inline w-4 h-4 -mt-0.5 xl:mr-1" />
|
||||||
icon={<PlusIcon className="inline w-4 h-4 -mt-0.5 xl:mr-1" />}
|
|
||||||
variant="gray"
|
|
||||||
>
|
|
||||||
<span className="hidden xl:inline-block">Add Device</span>
|
<span className="hidden xl:inline-block">Add Device</span>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,7 @@ import clsx from 'clsx';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Link, LinkProps } from 'react-router-dom';
|
import { Link, LinkProps } from 'react-router-dom';
|
||||||
|
|
||||||
export interface ButtonBaseProps extends VariantProps<typeof styles> {
|
export interface ButtonBaseProps extends VariantProps<typeof styles> {}
|
||||||
icon?: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ButtonProps = ButtonBaseProps &
|
export type ButtonProps = ButtonBaseProps &
|
||||||
React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||||
|
@ -69,7 +67,8 @@ const styles = cva(
|
||||||
colored: ['text-white shadow-sm hover:bg-opacity-90 active:bg-opacity-100'],
|
colored: ['text-white shadow-sm hover:bg-opacity-90 active:bg-opacity-100'],
|
||||||
selected: [
|
selected: [
|
||||||
'bg-gray-100 dark:bg-gray-500 text-black hover:text-black active:text-black dark:hover:text-white dark:text-white'
|
'bg-gray-100 dark:bg-gray-500 text-black hover:text-black active:text-black dark:hover:text-white dark:text-white'
|
||||||
]
|
],
|
||||||
|
bare: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
@ -86,21 +85,10 @@ export const Button = forwardRef<
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
className = clsx(styles(props), className);
|
className = clsx(styles(props), className);
|
||||||
|
|
||||||
let children = (
|
|
||||||
<>
|
|
||||||
{props.icon}
|
|
||||||
{props.children}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
return hasHref(props) ? (
|
return hasHref(props) ? (
|
||||||
<a {...props} ref={ref as any} className={clsx(className, 'no-underline inline-block')}>
|
<a {...props} ref={ref as any} className={clsx(className, 'no-underline inline-block')} />
|
||||||
{children}
|
|
||||||
</a>
|
|
||||||
) : (
|
) : (
|
||||||
<button {...(props as ButtonProps)} ref={ref as any} className={className}>
|
<button {...(props as ButtonProps)} ref={ref as any} className={className} />
|
||||||
{children}
|
|
||||||
</button>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,10 +104,7 @@ export const ButtonLink = forwardRef<
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={to} ref={ref as any} className={className}>
|
<Link to={to} ref={ref as any} className={className}>
|
||||||
<>
|
{props.children}
|
||||||
{props.icon}
|
|
||||||
{props.children}
|
|
||||||
</>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,28 +60,29 @@ export const SubMenu = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ITEM_CLASSES = `
|
const itemStyles = cva(
|
||||||
flex flex-row items-center justify-start flex-1
|
[
|
||||||
px-2 py-1 space-x-2
|
'flex flex-row items-center justify-start flex-1',
|
||||||
cursor-default rounded
|
'px-2 py-1 space-x-2',
|
||||||
focus:outline-none
|
'cursor-default rounded',
|
||||||
`;
|
'focus:outline-none'
|
||||||
|
],
|
||||||
const itemStyles = cva([ITEM_CLASSES], {
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: 'hover:bg-primary focus:bg-primary',
|
default: 'hover:bg-primary focus:bg-primary',
|
||||||
danger: `
|
danger: [
|
||||||
text-red-600 dark:text-red-400
|
'text-red-600 dark:text-red-400',
|
||||||
hover:text-white focus:text-white
|
'hover:text-white focus:text-white',
|
||||||
hover:bg-red-500 focus:bg-red-500
|
'hover:bg-red-500 focus:bg-red-500'
|
||||||
`
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'default'
|
||||||
}
|
}
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default'
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
interface ItemProps extends VariantProps<typeof itemStyles> {
|
interface ItemProps extends VariantProps<typeof itemStyles> {
|
||||||
icon?: Icon;
|
icon?: Icon;
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||||
|
|
||||||
import { Dropdown } from './Dropdown';
|
import { Root } from './Dropdown';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'UI/Dropdown',
|
title: 'UI/Dropdown',
|
||||||
component: Dropdown,
|
component: Root,
|
||||||
argTypes: {},
|
argTypes: {},
|
||||||
parameters: {
|
parameters: {
|
||||||
backgrounds: {
|
backgrounds: {
|
||||||
default: 'dark'
|
default: 'dark'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} as ComponentMeta<typeof Dropdown>;
|
} as ComponentMeta<typeof Root>;
|
||||||
|
|
||||||
const Template: ComponentStory<typeof Dropdown> = (args) => <Dropdown {...args} />;
|
const Template: ComponentStory<typeof Root> = (args) => <Root {...args} />;
|
||||||
|
|
||||||
export const Default = Template.bind({});
|
export const Default = Template.bind({});
|
||||||
Default.args = {
|
// Default.args = {
|
||||||
buttonText: 'Item 1',
|
// buttonText: 'Item 1',
|
||||||
items: [
|
// items: [
|
||||||
[
|
// [
|
||||||
{
|
// {
|
||||||
name: 'Item 1',
|
// name: 'Item 1',
|
||||||
selected: true
|
// selected: true
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'Item 2',
|
// name: 'Item 2',
|
||||||
selected: false
|
// selected: false
|
||||||
}
|
// }
|
||||||
]
|
// ]
|
||||||
]
|
// ]
|
||||||
};
|
// };
|
||||||
|
|
|
@ -1,155 +1,114 @@
|
||||||
import { Menu, Transition } from '@headlessui/react';
|
import { Menu, Transition } from '@headlessui/react';
|
||||||
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
import { ChevronDownIcon } from '@heroicons/react/24/solid';
|
||||||
|
import { VariantProps, cva } from 'class-variance-authority';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { Fragment, PropsWithChildren } from 'react';
|
import { Fragment, PropsWithChildren } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { Button } from './Button';
|
import * as UI from '.';
|
||||||
|
import { tw } from './utils';
|
||||||
|
|
||||||
export type DropdownItem = (
|
export const Section = tw.div`px-1 py-1 space-y-[2px]`;
|
||||||
| {
|
|
||||||
name: string;
|
|
||||||
icon?: any;
|
|
||||||
selected?: boolean;
|
|
||||||
to?: string;
|
|
||||||
wrapItemComponent?: React.FC<PropsWithChildren>;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
name: string;
|
|
||||||
icon?: any;
|
|
||||||
disabled?: boolean;
|
|
||||||
selected?: boolean;
|
|
||||||
onPress?: () => any;
|
|
||||||
to?: string;
|
|
||||||
wrapItemComponent?: React.FC<PropsWithChildren>;
|
|
||||||
}
|
|
||||||
)[];
|
|
||||||
|
|
||||||
export interface DropdownProps {
|
const itemStyles = cva(
|
||||||
items: DropdownItem[];
|
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
||||||
buttonText?: string;
|
{
|
||||||
buttonTextClassName?: string;
|
variants: {
|
||||||
buttonProps?: React.ComponentProps<typeof Button>;
|
selected: {
|
||||||
buttonComponent?: React.ReactNode;
|
true: 'bg-gray-300 dark:bg-primary dark:hover:bg-primary'
|
||||||
buttonIcon?: any;
|
},
|
||||||
|
active: {
|
||||||
|
true: ''
|
||||||
|
// false: 'text-gray-900 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const itemIconStyles = cva('mr-2 w-4 h-4', {
|
||||||
|
variants: {
|
||||||
|
active: {
|
||||||
|
true: 'dark:text-gray-100',
|
||||||
|
false: 'text-gray-600 dark:text-gray-200'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type DropdownItemProps =
|
||||||
|
| PropsWithChildren<{
|
||||||
|
to?: string;
|
||||||
|
className?: string;
|
||||||
|
icon?: any;
|
||||||
|
onClick?: () => void;
|
||||||
|
}> &
|
||||||
|
VariantProps<typeof itemStyles>;
|
||||||
|
|
||||||
|
export const Item = ({ to, className, icon: Icon, children, ...props }: DropdownItemProps) => {
|
||||||
|
let content = (
|
||||||
|
<>
|
||||||
|
{Icon && <Icon className={itemIconStyles(props)} />}
|
||||||
|
<span className="text-left">{children}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return to ? (
|
||||||
|
<Link {...props} to={to} className={clsx(itemStyles(props), className)}>
|
||||||
|
{content}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<button {...props} className={clsx(itemStyles(props), className)}>
|
||||||
|
{content}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Button = ({ children, ...props }: UI.ButtonProps) => {
|
||||||
|
return (
|
||||||
|
<UI.Button size="sm" {...props}>
|
||||||
|
{children}
|
||||||
|
<div className="flex-grow" />
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="w-5 h-5 ml-2 -mr-1 text-violet-200 hover:text-violet-100"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</UI.Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface DropdownRootProps {
|
||||||
|
button: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
itemsClassName?: string;
|
itemsClassName?: string;
|
||||||
itemButtonClassName?: string;
|
|
||||||
align?: 'left' | 'right';
|
align?: 'left' | 'right';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Dropdown: React.FC<DropdownProps> = (props) => {
|
export const Root = (props: PropsWithChildren<DropdownRootProps>) => {
|
||||||
return (
|
return (
|
||||||
<div className={clsx('w-full mt-2', props.className)}>
|
<Menu as="div" className={clsx('relative flex w-full text-left', props.className)}>
|
||||||
<Menu as="div" className="relative flex w-full text-left">
|
<Menu.Button as="div" className="flex-1 outline-none">
|
||||||
<Menu.Button as="div" className="flex-1 outline-none">
|
{props.button}
|
||||||
{props.buttonComponent ? (
|
</Menu.Button>
|
||||||
props.buttonComponent
|
|
||||||
) : (
|
<Transition
|
||||||
<Button size="sm" {...props.buttonProps}>
|
as={Fragment}
|
||||||
{props.buttonIcon}
|
enter="transition duration-100 ease-out"
|
||||||
{props.buttonText && (
|
enterFrom="transform scale-95 opacity-0"
|
||||||
<>
|
enterTo="transform scale-100 opacity-100"
|
||||||
<span className={clsx('w-32 truncate', props.buttonTextClassName)}>
|
leave="transition duration-75 ease-out"
|
||||||
{props.buttonText}
|
leaveFrom="transform scale-100 opacity-100"
|
||||||
</span>
|
leaveTo="transform scale-95 opacity-0"
|
||||||
<div className="flex-grow" />
|
>
|
||||||
<ChevronDownIcon
|
<Menu.Items
|
||||||
className="w-5 h-5 ml-2 -mr-1 text-violet-200 hover:text-violet-100 "
|
className={clsx(
|
||||||
aria-hidden="true"
|
'absolute z-50 min-w-fit w-full bg-white border divide-y divide-gray-100 rounded shadow-xl top-full dark:bg-gray-550 dark:divide-gray-500 dark:border-gray-600 ring-1 ring-black ring-opacity-5 focus:outline-none',
|
||||||
/>
|
props.itemsClassName,
|
||||||
</>
|
{ 'left-0': props.align === 'left' },
|
||||||
)}
|
{ 'right-0': props.align === 'right' }
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Menu.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition duration-100 ease-out"
|
|
||||||
enterFrom="transform scale-95 opacity-0"
|
|
||||||
enterTo="transform scale-100 opacity-100"
|
|
||||||
leave="transition duration-75 ease-out"
|
|
||||||
leaveFrom="transform scale-100 opacity-100"
|
|
||||||
leaveTo="transform scale-95 opacity-0"
|
|
||||||
>
|
>
|
||||||
<Menu.Items
|
{props.children}
|
||||||
className={clsx(
|
</Menu.Items>
|
||||||
'absolute z-50 min-w-fit w-full bg-white border divide-y divide-gray-100 rounded shadow-xl top-full dark:bg-gray-550 dark:divide-gray-500 dark:border-gray-600 ring-1 ring-black ring-opacity-5 focus:outline-none',
|
</Transition>
|
||||||
props.itemsClassName,
|
</Menu>
|
||||||
{ 'left-0': props.align === 'left' },
|
|
||||||
{ 'right-0': props.align === 'right' }
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{props.items.map((item, index) => (
|
|
||||||
<div key={index} className="px-1 py-1 space-y-[2px]">
|
|
||||||
{item.map((button, index) => (
|
|
||||||
<Menu.Item key={index}>
|
|
||||||
{({ active }) => {
|
|
||||||
const WrappedItem: any = button.wrapItemComponent
|
|
||||||
? button.wrapItemComponent
|
|
||||||
: (props: React.PropsWithChildren) => <>{props.children}</>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WrappedItem>
|
|
||||||
{button.to ? (
|
|
||||||
<Link
|
|
||||||
to={button.to}
|
|
||||||
className={clsx(
|
|
||||||
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
|
||||||
{
|
|
||||||
'bg-gray-300 dark:bg-primary dark:hover:bg-primary':
|
|
||||||
button.selected
|
|
||||||
// 'text-gray-900 dark:text-gray-200': !active
|
|
||||||
},
|
|
||||||
props.itemButtonClassName
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{button.icon && (
|
|
||||||
<button.icon
|
|
||||||
className={clsx('mr-2 w-4 h-4', {
|
|
||||||
'dark:text-gray-100': active,
|
|
||||||
'text-gray-600 dark:text-gray-200': !active
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="text-left">{button.name}</span>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<button
|
|
||||||
onClick={(button as any).onPress}
|
|
||||||
disabled={(button as any)?.disabled === true}
|
|
||||||
className={clsx(
|
|
||||||
'text-sm group flex grow shrink-0 rounded items-center w-full whitespace-nowrap px-2 py-1 mb-[2px] dark:hover:bg-gray-650 disabled:opacity-50 disabled:cursor-not-allowed',
|
|
||||||
{
|
|
||||||
'bg-gray-300 dark:bg-primary dark:hover:bg-primary':
|
|
||||||
button.selected
|
|
||||||
// 'text-gray-900 dark:text-gray-200': !active
|
|
||||||
},
|
|
||||||
props.itemButtonClassName
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{button.icon && (
|
|
||||||
<button.icon
|
|
||||||
className={clsx('mr-2 w-4 h-4', {
|
|
||||||
'dark:text-gray-100': active,
|
|
||||||
'text-gray-600 dark:text-gray-200': !active
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<span className="text-left">{button.name}</span>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</WrappedItem>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Menu.Items>
|
|
||||||
</Transition>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export * from './Button';
|
export * from './Button';
|
||||||
export * from './Dropdown';
|
export * as Dropdown from './Dropdown';
|
||||||
export * from './Dialog';
|
export * from './Dialog';
|
||||||
export * from './Loader';
|
export * from './Loader';
|
||||||
export * as ContextMenu from './ContextMenu';
|
export * as ContextMenu from './ContextMenu';
|
||||||
|
@ -8,5 +8,5 @@ export * from './Input';
|
||||||
export * from './Select';
|
export * from './Select';
|
||||||
export * as Tabs from './Tabs';
|
export * as Tabs from './Tabs';
|
||||||
export * from './Typography';
|
export * from './Typography';
|
||||||
export { tw } from './utils';
|
export * from './utils';
|
||||||
export { cva } from 'class-variance-authority';
|
export { cva } from 'class-variance-authority';
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
function twFactory(element: any) {
|
function twFactory(element: any) {
|
||||||
return ([className, ..._]: TemplateStringsArray) => {
|
return ([className, ..._]: TemplateStringsArray) => {
|
||||||
const Component = React.forwardRef(({ className: pClassName, ...props }: any, ref) =>
|
return restyle(element)(() => className);
|
||||||
React.createElement(element, {
|
|
||||||
...props,
|
|
||||||
className: [className, pClassName],
|
|
||||||
ref
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return Component;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,3 +21,21 @@ export const tw = new Proxy((() => {}) as unknown as TailwindFactory, {
|
||||||
get: (_, property: string) => twFactory(property),
|
get: (_, property: string) => twFactory(property),
|
||||||
apply: (_, __, [el]: [React.ReactElement]) => twFactory(el)
|
apply: (_, __, [el]: [React.ReactElement]) => twFactory(el)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const restyle = <
|
||||||
|
T extends
|
||||||
|
| string
|
||||||
|
| React.FunctionComponent<{ className: string }>
|
||||||
|
| React.ComponentClass<{ className: string }>
|
||||||
|
>(
|
||||||
|
element: T
|
||||||
|
) => {
|
||||||
|
return (cls: () => string) =>
|
||||||
|
React.forwardRef(({ className, ...props }: any, ref) =>
|
||||||
|
React.createElement(element, {
|
||||||
|
...props,
|
||||||
|
className: clsx(cls(), className),
|
||||||
|
ref
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue