Skip to content

Commit

Permalink
feat(pixels): refactor Modal sizes + update tailwind variants
Browse files Browse the repository at this point in the history
  • Loading branch information
toxsick committed Apr 24, 2024
1 parent 37cb1bb commit 8f80363
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 178 deletions.
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@
"!reference sequence"
],

// tailwind extension settings
// disable tailwind extension validate (because it sometimes hangs and we use prettier-plugin-tailwindcss)
// see: https://github.com/tailwindlabs/tailwindcss-intellisense/issues/755#issuecomment-1736458946
"tailwindCSS.validate": false,
// configure tailwind extension to work with tailwind-variants (tv) and cn (short for classNames)
// SEE: https://www.tailwind-variants.org/docs/getting-started#intellisense-setup-optional
// see: https://www.tailwind-variants.org/docs/getting-started#intellisense-setup-optional
"tailwindCSS.experimental.classRegex": [
["cn\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]
Expand Down
3 changes: 1 addition & 2 deletions packages/pixels/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@
"@nextui-org/system": "2.1.2",
"@nextui-org/tooltip": "2.0.33",
"classnames": "2.5.1",
"debug": "4.3.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "5.1.0",
Expand All @@ -108,4 +107,4 @@
"@types/react": "18.2.79",
"@types/react-dom": "18.2.25"
}
}
}
71 changes: 26 additions & 45 deletions packages/pixels/src/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ import {
CardHeader as NextCardHeader,
} from '@nextui-org/card';
import { Divider as NextDivider } from '@nextui-org/divider';
import createDebug from 'debug';
import { tv } from 'tailwind-variants';

const debug = createDebug('component:Card');

// card styling variants
export const cardVariants = tv({
slots: {
base: 'border border-slate-300',
base: 'border border-divider',
body: '',
divider: 'my-0 bg-slate-300',
divider: 'my-0 border-divider',
footer: '',
header: 'text-base font-semibold',
},
Expand All @@ -30,7 +27,7 @@ type CardVariantSlots = Partial<
>;

export interface CardProps extends CardVariantProps {
/** child components */
/** card body content */
children?: ReactNode;
/** CSS class name */
className?: string | CardVariantSlots;
Expand All @@ -52,63 +49,47 @@ const Card = ({
header = undefined,
footer = undefined,
}: CardProps) => {
debug('Card', { className, testId });
const {
base: baseSlot,
body: bodySlot,
divider: dividerSlot,
footer: footerSlot,
header: headerSlot,
} = cardVariants();
// classNames from slots
const variants = cardVariants();
const classNameObj = (typeof className === 'object' && className) || {};
const classNames = {
base: variants.base({
className: classNameObj.base || (className as string),
}),
header: variants.header({ className: classNameObj.header }),
body: variants.body({ className: classNameObj.body }),
footer: variants.footer({ className: classNameObj.footer }),
};

const divider = (
<NextDivider
className={variants.divider({ className: classNameObj.divider })}
/>
);

return (
<NextCard
classNames={classNames}
data-testid={testId && `card_${testId}`}
className={baseSlot({
className: typeof className === 'object' ? className.base : className,
})}
fullWidth
radius="sm"
shadow="none"
>
{header && (
<>
<NextCardHeader
data-testid={testId && `card_header_${testId}`}
className={headerSlot({
className: typeof className === 'object' && className.header,
})}
>
<NextCardHeader data-testid={testId && `card_header_${testId}`}>
{header}
</NextCardHeader>
<NextDivider
className={dividerSlot({
className: typeof className === 'object' && className.divider,
})}
/>
{divider}
</>
)}
<NextCardBody
data-testid={testId && `card_body_${testId}`}
className={bodySlot({
className: typeof className === 'object' && className.body,
})}
>
<NextCardBody data-testid={testId && `card_body_${testId}`}>
{children}
</NextCardBody>
{footer && (
<>
<NextDivider
className={dividerSlot({
className: typeof className === 'object' && className.divider,
})}
/>
<NextCardFooter
data-testid={testId && `card_footer_${testId}`}
className={footerSlot({
className: typeof className === 'object' && className.footer,
})}
>
{divider}
<NextCardFooter data-testid={testId && `card_footer_${testId}`}>
{footer}
</NextCardFooter>
</>
Expand Down
14 changes: 7 additions & 7 deletions packages/pixels/src/Card/__snapshots__/Card.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`Story Snapshots > CustomSlotStyles 1`] = `
<div>
<div
class="flex flex-col relative overflow-hidden height-auto box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-slate-300 text-blue-400"
class="flex flex-col relative overflow-hidden height-auto box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-divider text-blue-400"
tabindex="-1"
>
<div
Expand All @@ -12,7 +12,7 @@ exports[`Story Snapshots > CustomSlotStyles 1`] = `
Header
</div>
<hr
class="shrink-0 border-none w-full h-divider my-0 bg-yellow-400"
class="shrink-0 border-none w-full h-divider my-0 border-divider bg-yellow-400"
role="separator"
/>
<div
Expand All @@ -25,7 +25,7 @@ exports[`Story Snapshots > CustomSlotStyles 1`] = `
</p>
</div>
<hr
class="shrink-0 border-none w-full h-divider my-0 bg-yellow-400"
class="shrink-0 border-none w-full h-divider my-0 border-divider bg-yellow-400"
role="separator"
/>
<div
Expand All @@ -40,7 +40,7 @@ exports[`Story Snapshots > CustomSlotStyles 1`] = `
exports[`Story Snapshots > Default 1`] = `
<div>
<div
class="flex flex-col relative overflow-hidden height-auto text-foreground box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-slate-300"
class="flex flex-col relative overflow-hidden height-auto text-foreground box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-divider"
tabindex="-1"
>
<div
Expand All @@ -59,7 +59,7 @@ exports[`Story Snapshots > Default 1`] = `
exports[`Story Snapshots > FooterAndHeader 1`] = `
<div>
<div
class="flex flex-col relative overflow-hidden height-auto text-foreground box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-slate-300"
class="flex flex-col relative overflow-hidden height-auto text-foreground box-border bg-content1 outline-none data-[focus-visible=true]:z-10 data-[focus-visible=true]:outline-2 data-[focus-visible=true]:outline-focus data-[focus-visible=true]:outline-offset-2 shadow-none rounded-small w-full transition-transform-background motion-reduce:transition-none border border-divider"
tabindex="-1"
>
<div
Expand All @@ -68,7 +68,7 @@ exports[`Story Snapshots > FooterAndHeader 1`] = `
Header
</div>
<hr
class="shrink-0 border-none w-full h-divider my-0 bg-slate-300"
class="shrink-0 bg-divider border-none w-full h-divider my-0 border-divider"
role="separator"
/>
<div
Expand All @@ -81,7 +81,7 @@ exports[`Story Snapshots > FooterAndHeader 1`] = `
</p>
</div>
<hr
class="shrink-0 border-none w-full h-divider my-0 bg-slate-300"
class="shrink-0 bg-divider border-none w-full h-divider my-0 border-divider"
role="separator"
/>
<div
Expand Down
5 changes: 0 additions & 5 deletions packages/pixels/src/Menu/Menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import {
DropdownTrigger as NextDropdownTrigger,
} from '@nextui-org/dropdown';
import cn from 'classnames';
import createDebug from 'debug';

const debug = createDebug('component:Menu');

/**
* Menu item type
Expand Down Expand Up @@ -103,8 +100,6 @@ const Menu = ({
isDisabled = false,
items,
}: MenuProps) => {
debug('Menu', { items });

return (
<NextDropdown isDisabled={isDisabled}>
<NextDropdownTrigger className={cn(className)} data-testid={testId}>
Expand Down
128 changes: 85 additions & 43 deletions packages/pixels/src/Modal/Modal.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,79 +1,121 @@
import type { Meta, StoryObj } from '@storybook/react';
import type { ModalProps } from './Modal';

import { Fragment, useState } from 'react';
import { useState } from 'react';

import Modal, { modalSizeOptions } from './Modal';
import { expect, userEvent, within } from '@storybook/test';

import Button from '../Button';
import Modal, { modalVariants } from './Modal';
import { longContent } from './storyData';

const meta: Meta<typeof Modal> = {
title: 'pixels/Modal',
component: Modal,
};

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

export const Default: Story = {
argTypes: {
size: {
control: { type: 'radio' },
options: Object.keys(modalVariants.variants.size),
},
},
render: (args) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const [open, setOpen] = useState(false);
const toggleOpen = () => {
setOpen(!open);
};
return (
<>
<button type="button" onClick={toggleOpen}>
trigger
</button>
<Modal
{...args}
isOpen={open}
onClose={() => {
setOpen(false);
}}
>
children
</Modal>
<Button onClick={() => setOpen(true)} testId="modal_trigger">
Open Modal
</Button>
<Modal {...args} isOpen={open} onClose={() => setOpen(false)} />
</>
);
},
};

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

const openModal: Story['play'] = async ({ canvasElement }) => {
const body = within(canvasElement?.parentElement as HTMLElement);
const canvas = within(canvasElement);
const trigger = canvas.getByTestId('modal_trigger');
await userEvent.click(trigger);
expect(body.getByTestId('modal')).toBeTruthy();
};

export const Default: Story = {
args: {
header: 'Modal Header',
children: 'Modal Content',
},
};

export const WithHeaderAndFooter: Story = {
args: {
title: 'Modal Title',
header: 'Modal Header',
children: 'Modal Content',
footer: <Button>Some Action</Button>,
},
};

export const AllSizesTemplate: Story = {
export const ScrollLongContent: Story = {
args: {
title: 'Size',
header: 'Modal Header',
children: longContent,
},
play: openModal,
};

export const CustomStyles: Story = {
args: {
header: 'Custom Styles',
children: 'This is very custom!',
className: {
body: 'py-6',
backdrop: 'bg-[#292f46]/50 backdrop-opacity-40',
base: 'border-[#292f46] bg-[#19172c] dark:bg-[#19172c] text-[#a8b0d3]',
header: 'border-b-[1px] border-[#292f46]',
footer: 'border-t-[1px] border-[#292f46]',
closeButton: 'hover:bg-white/5 active:bg-white/10',
},
},
play: openModal,
};

export const AllSizes: Story = {
args: {
header: 'Size',
},
render: (args) => (
<>
{modalSizeOptions.map((size) => {
const [open, setOpen] = useState(false);
const toggleOpen = () => {
setOpen(!open);
};
{Object.keys(modalVariants.variants.size).map((size) => {
const [content, setContent] = useState<string | JSX.Element | false>(
false,
);
return (
<Fragment key={size}>
<button type="button" onClick={toggleOpen}>
<div key={size} className="mt-2">
<Button
onClick={() => setContent(`short ${size} content`)}
className="mr-2"
>
{size}
</button>
</Button>
<Button onClick={() => setContent(longContent)}>
{size} scroll
</Button>
<Modal
{...args}
size={size}
isOpen={open}
onClose={() => {
setOpen(false);
}}
header={`Size ${size} Modal`}
isOpen={!!content}
onClose={() => setContent(false)}
size={size as ModalProps['size']}
>
{size}
{content}
</Modal>
</Fragment>
</div>
);
})}
</>
),
name: 'All sizes',
argTypes: {
// do not show size in controls table
size: {
Expand Down
Loading

0 comments on commit 8f80363

Please sign in to comment.