Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tray styling #261

Merged
merged 7 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 152 additions & 38 deletions packages/utah-design-system/src/components/Drawer.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,120 @@
import { useOverlayTrigger } from 'react-aria';
import { AriaButtonProps, FocusScope, useOverlayTrigger } from 'react-aria';
import { DOMProps } from '@react-types/shared';
import { Button } from './Button';
import { ChevronLeftIcon } from '@heroicons/react/24/solid';
import { twJoin } from 'tailwind-merge';
import {
ChevronLeftIcon,
ChevronDownIcon,
ArrowsPointingOutIcon,
} from '@heroicons/react/24/solid';
import { twJoin, twMerge } from 'tailwind-merge';
import { tv } from 'tailwind-variants';
import { useState } from 'react';
import { OverlayTriggerState } from 'react-stately';

/*
Getting Started

`npm install react-aria react-stately`

const drawerState = useOverlayTriggerState({});
const overlayTriggerProps = useOverlayTrigger(
const drawerTriggerProps = useOverlayTrigger(
{
type: 'dialog',
},
drawerState,
);

<Drawer state={drawerState} {...overlayTriggerProps}>
<Drawer state={drawerState} {...drawerTriggerProps}>
<div className='p-4'>Drawer Content</div>
</Drawer>

*/
/* default to open
const state = useOverlayTrigger({ defaultOpen: true });
<Drawer state={state} />
const drawerState = useOverlayTrigger({ defaultOpen: true });
<Drawer state={drawerState} />
*/

const drawer = tv({
slots: {
container:
'group shrink-0 bg-white duration-500 ease-in-out dark:bg-zinc-800',
content: 'text-zinc-900 duration-500 ease-in-out dark:text-zinc-100',
trigger: 'absolute z-10 overflow-hidden rounded border-zinc-200 bg-white ',
triggerButton:
'flex items-center justify-center rounded-none border-0 bg-transparent p-1 shadow-md hover:bg-current group-hover:bg-black/10 dark:group-hover:bg-white/10',
},
variants: {
type: {
sidebar: {
container:
"relative h-full w-80 transition-[width] data-[open='false']:w-0",
content:
"h-full w-80 overflow-y-auto data-[open='false']:-translate-x-full data-[open='true']:translate-x-0",
trigger:
'h-10 w-6 -right-8 top-[calc(50%-24px)] rounded-l-none border-l-0 dark:bg-zinc-800 dark:border-zinc-700',
triggerButton: 'h-10 w-full',
},
tray: {
container:
'bg-zinc-50 dark:bg-zinc-700 absolute inset-x-0 bottom-0 h-80 transition-[height] data-[open="false"]:h-0 data-[open="true"]:h-80',
content:
"overflow-x-auto data-[open='false']:h-0 data-[open='true']:h-80 data-[open='true']:translate-y-0 data-[open='false']:overflow-hidden",
trigger:
'w-10 h-6 -top-6 bg-zinc-50 -top-6 left-[calc(50%-24px)] rounded-b-none border-b-0 dark:bg-zinc-700 dark:border-zinc-800',
triggerButton: 'h-6 w-10',
},
},
size: {
normal: null,
fullscreen: null,
},
},
defaultVariants: {
type: 'sidebar',
size: 'normal',
},
compoundVariants: [
{
type: 'tray',
size: 'fullscreen',
class: {
container:
'data-[open="true"]:fixed data-[open="true"]:h-full data-[open="true"]:z-30',
},
},
{
type: 'sidebar',
size: 'fullscreen',
class: {
container: 'data-[open="true"]:w-full',
content: 'data-[open="true"]:w-full',
},
},
],
});

export type DrawerProps = {
allowFullScreen?: boolean; // set to true to show the fullscreen button
children: React.ReactNode;
className?: string;
main?: boolean;
overlayProps?: DOMProps;
state: OverlayTriggerState;
triggerProps?: AriaButtonProps;
type?: 'sidebar' | 'tray';
};

export const Drawer = ({
triggerProps,
children,
className,
overlayProps,
state,
children,
type = 'sidebar',
triggerProps,
allowFullScreen = false,
main = false,
}) => {
type = 'sidebar',
}: DrawerProps) => {
const [size, setSize] = useState<'normal' | 'fullscreen'>('normal');
if (!triggerProps || !overlayProps) {
throw new Error('You must provide both triggerProps and overlayProps');
}
Expand All @@ -43,43 +124,76 @@ export const Drawer = ({
overlayProps: internalOverlayProps,
} = useOverlayTrigger({ type: 'dialog' }, state);

const css = drawer({ type, size });

return (
<div
data-open={state.isOpen}
data-type={type}
className="relative h-full w-80 shrink-0 transition-[width] duration-500 ease-in-out data-[open='false']:w-0"
className={twMerge(css.container(), className)}
id={main ? 'main-content' : undefined}
>
<aside
data-type={type}
data-open={state.isOpen}
{...(overlayProps || internalOverlayProps)}
className='h-full w-80 overflow-y-auto text-zinc-900 duration-500 ease-in-out data-[type="sidebar"]:data-[open="false"]:-translate-x-full data-[type="sidebar"]:data-[open="true"]:translate-x-0 dark:text-zinc-100'
>
{children}
</aside>
<FocusScope contain={size === 'fullscreen'} restoreFocus>
<aside
data-open={state.isOpen}
className={css.content()}
{...(overlayProps || internalOverlayProps)}
>
{allowFullScreen && (
<Button
aria-label="Toggle full-screen"
className="absolute right-0 top-0 p-2"
variant="icon"
onPress={() =>
setSize(size === 'normal' ? 'fullscreen' : 'normal')
}
>
<ArrowsPointingOutIcon className="h-full w-6 shrink-0 fill-zinc-900 dark:fill-white" />
</Button>
)}
{children}
</aside>
</FocusScope>
<DefaultDrawerTrigger
triggerProps={triggerProps || internalTriggerProps}
state={state}
Icon={type === 'sidebar' ? ChevronLeftIcon : ChevronDownIcon}
className={css.trigger()}
buttonClassName={css.triggerButton()}
/>
</div>
);
};

const DefaultDrawerTrigger = ({ triggerProps, state }) => (
<div className="group absolute -right-8 top-[calc(50%-24px)] z-10 w-6 overflow-hidden rounded rounded-l-none border-l-0 border-zinc-200 bg-white dark:border-zinc-700 dark:bg-zinc-800">
<Button
className="flex h-10 w-6 items-center justify-center rounded-none border-0 bg-transparent p-1 shadow-md hover:bg-current group-hover:bg-black/10 dark:group-hover:bg-white/10"
{...triggerProps}
onPress={state.toggle}
aria-label="Close the drawer"
>
<ChevronLeftIcon
className={twJoin(
'h-6 w-full shrink-0 fill-zinc-900 transition-transform duration-500 dark:fill-white',
!state.isOpen ? '-rotate-180' : '',
)}
/>
</Button>
</div>
);
export type DefaultDrawerTriggerProps = {
triggerProps: AriaButtonProps;
Icon: React.ElementType;
state: OverlayTriggerState;
className?: string;
buttonClassName?: string;
};

const DefaultDrawerTrigger = ({
triggerProps,
Icon,
state,
className,
buttonClassName,
}: DefaultDrawerTriggerProps) => {
return (
<div className={className}>
<Button
className={buttonClassName}
{...triggerProps}
onPress={state.toggle}
aria-label="Close the drawer"
>
<Icon
className={twJoin(
'size-full shrink-0 fill-zinc-900 transition-transform duration-500 dark:fill-white',
!state.isOpen ? '-rotate-180' : '',
)}
/>
</Button>
</div>
);
};
4 changes: 2 additions & 2 deletions packages/utah-design-system/src/components/SocialMedia.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
export const SocialMedia = () => (
<div className="flex flex-col items-center gap-1 bg-primary-900 px-8 py-2 text-zinc-50 dark:bg-zinc-800 sm:flex-row sm:gap-4 sm:py-0">
<div className="flex flex-col items-center gap-1 bg-primary-900 py-2 text-zinc-50 dark:bg-zinc-800 sm:flex-row sm:gap-4 sm:py-0 md:px-8">
<div className="order-last text-sm font-medium uppercase sm:order-first">
Connect with us
</div>
<div className="sm:flex-0 flex items-center gap-1">
<div className="flex items-center gap-1 sm:flex-none">
<a
href="mailto:[email protected]"
className="custom-style focus:ring-accent flex grow items-center justify-center rounded-full p-2 transition-all ease-in-out hover:bg-white/20 focus:outline-none focus:ring-4"
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"jsx": "react-jsx",
"skipLibCheck": true
},
"include": ["src", "stories"]
"include": ["packages", "src"]
}