feat: add modal and icon button

main
Juan Olvera 9 months ago
parent a9f052f488
commit 91d26fb0af

@ -695,6 +695,10 @@ select {
pointer-events: none;
}
.fixed {
position: fixed;
}
.absolute {
position: absolute;
}
@ -703,6 +707,13 @@ select {
position: relative;
}
.inset-0 {
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
}
.inset-y-0 {
top: 0px;
bottom: 0px;
@ -724,10 +735,54 @@ select {
right: 0px;
}
.right-4 {
right: 1rem;
}
.top-4 {
top: 1rem;
}
.top-1\/2 {
top: 50%;
}
.left-1\/2 {
left: 50%;
}
.top-1 {
top: 0.25rem;
}
.right-2 {
right: 0.5rem;
}
.right-1 {
right: 0.25rem;
}
.top-2 {
top: 0.5rem;
}
.right-3 {
right: 0.75rem;
}
.top-3 {
top: 0.75rem;
}
.isolate {
isolation: isolate;
}
.z-50 {
z-index: 50;
}
.m-0 {
margin: 0px;
}
@ -756,6 +811,10 @@ select {
display: inline-flex;
}
.grid {
display: grid;
}
.h-full {
height: 100%;
}
@ -772,10 +831,18 @@ select {
width: 24rem;
}
.w-56 {
width: 14rem;
}
.min-w-0 {
min-width: 0px;
}
.max-w-lg {
max-width: 32rem;
}
.-translate-x-2\/4 {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
@ -786,6 +853,30 @@ select {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-x-\[-50\%\] {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.translate-y-\[-50\%\] {
--tw-translate-y: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.-translate-x-1\/2 {
--tw-translate-x: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.-translate-y-1\/2 {
--tw-translate-y: -50%;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.items-center {
align-items: center;
}
@ -798,6 +889,10 @@ select {
justify-content: space-between;
}
.gap-4 {
gap: 1rem;
}
.gap-x-2 {
-moz-column-gap: 0.5rem;
column-gap: 0.5rem;
@ -839,6 +934,18 @@ select {
border-radius: 0.5rem;
}
.rounded-sm {
border-radius: 0.125rem;
}
.rounded-md {
border-radius: 0.375rem;
}
.rounded-full {
border-radius: 9999px;
}
.border-2 {
border-width: 2px;
}
@ -847,6 +954,10 @@ select {
border-width: 1px;
}
.border-0 {
border-width: 0px;
}
.border-mono-border {
--tw-border-opacity: 1;
border-color: rgb(209 213 219 / var(--tw-border-opacity));
@ -877,6 +988,23 @@ select {
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}
.bg-black\/80 {
background-color: rgb(0 0 0 / 0.8);
}
.bg-black {
--tw-bg-opacity: 1;
background-color: rgb(0 0 0 / var(--tw-bg-opacity));
}
.bg-transparent {
background-color: transparent;
}
.bg-opacity-50 {
--tw-bg-opacity: 0.5;
}
.p-0 {
padding: 0px;
}
@ -997,6 +1125,11 @@ select {
line-height: 1.75rem;
}
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.font-semibold {
font-weight: 600;
}
@ -1029,11 +1162,31 @@ select {
opacity: 0;
}
.opacity-70 {
opacity: 0.7;
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.outline-none {
outline: 2px solid transparent;
outline-offset: 2px;
}
.transition-opacity {
transition-property: opacity;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}
.duration-200 {
transition-duration: 200ms;
}
html {
font-size: 14px;
}
@ -1058,6 +1211,10 @@ html {
background-color: rgb(63 63 70 / var(--tw-bg-opacity));
}
.hover\:opacity-100:hover {
opacity: 1;
}
.focus\:border-mono-primary:focus {
--tw-border-opacity: 1;
border-color: rgb(39 39 42 / var(--tw-border-opacity));
@ -1074,6 +1231,16 @@ html {
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-2:focus {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
.focus\:ring-offset-2:focus {
--tw-ring-offset-width: 2px;
}
.focus-visible\:ring-2:focus-visible {
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
@ -1089,6 +1256,10 @@ html {
--tw-ring-offset-width: 2px;
}
.disabled\:pointer-events-none:disabled {
pointer-events: none;
}
.data-\[disabled\]\:opacity-50[data-disabled] {
opacity: 0.5;
}
@ -1122,6 +1293,10 @@ html {
}
@media (min-width: 640px) {
.sm\:rounded-lg {
border-radius: 0.5rem;
}
.sm\:px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;

1745
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -77,15 +77,17 @@
"webpack": "^5.77.0"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-primitive": "^1.0.3",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toggle": "^1.0.3",
"@react-spring/web": "^9.7.1",
"@tanstack/react-table": "^8.8.5",
"dashify": "^2.0.0",
"react-icons": "^4.7.1",
"react-icons": "^4.12.0",
"react-number-format": "^5.1.4",
"ts-pattern": "^5.0.6",
"zustand": "^4.3.7"

@ -1,6 +1,10 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Box } from "./Box";
import { Input } from "../Input";
import { Button } from "../Button";
import { Divider } from "../Divider";
import { cx } from "../../utils";
const meta: Meta<typeof Box> = {
title: "UI/Box",
@ -16,10 +20,18 @@ export const Default: Story = {
export const AsForm: Story = {
render: () => (
<Box asChild>
<form>
<input type="text" />
<button type="submit">Submit</button>
<Box asChild className={cx(["w-56"])}>
<form
onSubmit={(event) => {
event.preventDefault();
console.log("event", event);
}}
>
<Input label="Username" />
<Divider />
<Button type="submit" intent="primary">
Submit
</Button>
</form>
</Box>
),

@ -49,6 +49,20 @@ export const WithIcon: Story = {
),
};
export const OnlyIcon: Story = {
render: () => (
<Box className={cx(["space-x-2"])}>
<Button onlyIcon aria-label="Click me">
<FaBeer />
</Button>
<Button intent="primary" onlyIcon aria-label="Click me">
<FaApple />
</Button>
</Box>
),
};
export const Loading: Story = {
render: () => (
<Box className={cx(["space-x-2"])}>

@ -5,7 +5,6 @@ const base = [
"relative isolate inline-flex items-center justify-center gap-x-2 rounded-lg border-2 text-base/6 font-semibold",
// Focus
// "focus:outline-none data-[focus]:outline data-[focus]:outline-2 data-[focus]:outline-offset-2 data-[focus]:outline-blue-500",
"focus:outline-none",
"data-[focus]:outline",
"focus-visible:ring-2",
@ -36,11 +35,14 @@ const primary = [
"text-white",
];
const onlyIcon = ["rounded-full", "p-1"];
const buttonStyles = cva(base, {
variants: {
primary: {
true: primary,
},
intent: {
primary,
DEFAULT,
@ -52,6 +54,9 @@ const buttonStyles = cva(base, {
disabled: {
true: ["data-[disabled]:opacity-50"],
},
onlyIcon: {
true: onlyIcon,
},
},
defaultVariants: {
size: "md",

@ -2,23 +2,23 @@ import React, { forwardRef } from "react";
import { match, P } from "ts-pattern";
import { Primitive } from "@radix-ui/react-primitive";
import { CgSpinner } from "react-icons/cg";
import { buttonStyles } from "./Button.styles";
import * as styles from "./Button.styles";
import type * as T from "./Button.types";
import { cx } from "../../utils";
const Button = forwardRef<T.ButtonElement, T.ButtonProps>(
(
{ size, intent, primary, loading = false, children, ...props },
{ size, onlyIcon, intent, primary, loading = false, children, ...props },
forwardedRef
) => {
const classes = cx(
buttonStyles({ size, intent, primary }),
styles.buttonStyles({ size, intent, primary, onlyIcon }),
props.className
);
return (
<Primitive.button {...props} className={classes} ref={forwardedRef}>
{match({ loading })
{match({ loading, onlyIcon })
.with({ loading: true }, () => (
<>
<span
@ -35,6 +35,7 @@ const Button = forwardRef<T.ButtonElement, T.ButtonProps>(
<span className={cx(["opacity-0"])}>{children}</span>
</>
))
// .with({ onlyIcon: true }, () => <span>{children}</span>)
.otherwise(() => children)}
</Primitive.button>
);

@ -26,15 +26,15 @@ export const Default: Story = {
</Card.Header>
<Card.Body>
<form onSubmit={(event) => event.preventDefault()}>
<Input label="Name" fullWidth />
<Input label="Name" />
<Box className={cx(["py-2"])} />
<Input label="Type" fullWidth />
<Input label="Type" />
<Box className={cx(["py-2"])} />
<Input label="Balance" money fullWidth />
<Input label="Balance" money />
</form>
</Card.Body>
<Card.Footer className={cx(["flex", "justify-between"])}>

@ -4,7 +4,6 @@ const base = [
"overflow-hidden",
"rounded-lg",
"bg-white",
"border-2",
"divide-y",
"divide-y-2",
"text-mono-text",
@ -16,9 +15,14 @@ const cardStyles = cva(base, {
true: ["p-0"],
false: ["px-4", "py-4", "sm:px-6", "divide-y-0"],
},
borderless: {
true: ["border-0"],
false: ["border-2"],
},
},
defaultVariants: {
paddless: false,
borderless: false,
},
});

@ -7,8 +7,8 @@ import { Header, Title, Description, Body, Footer } from "./components";
import { cx } from "../../utils";
const Card = React.forwardRef<T.CardElement, T.CardProps>(
({ children, paddless, ...props }, forwardedRef) => {
const classes = cx(cardStyles({ paddless }), props.className);
({ children, paddless, borderless = false, ...props }, forwardedRef) => {
const classes = cx(cardStyles({ paddless, borderless }), props.className);
return (
<Primitive.div {...props} className={classes} ref={forwardedRef}>

@ -1,7 +1,7 @@
import * as React from "react";
import { match, P } from "ts-pattern";
import * as T from "./Divider.types";
import { Box } from "../Box";
import { Modal } from "../Box";
import { cx } from "../../utils";
import { dividerStyles } from "./Divider.styles";
@ -16,7 +16,7 @@ const Divider = React.forwardRef<
return match({ border })
.with({ border: true }, () => (
<div {...props} className={classes}>
<Box className={cx(["bg-mono-border", "h-[2px]"])} />
<Modal className={cx(["bg-mono-border", "h-[2px]"])} />
</div>
))
.otherwise(() => <div {...props} className={classes} ref={forwardedRef} />);

@ -0,0 +1 @@
export * from "./Divider";

@ -0,0 +1,48 @@
import * as React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Box } from "../Box";
import { Button } from "../Button";
import * as Card from "../Card";
import { Modal } from "./Modal";
import { cx } from "../../utils";
import { Input } from "../Input";
const meta: Meta<typeof Modal> = {
title: "UI/Modal",
component: Modal,
};
export default meta;
type Story = StoryObj<typeof Modal>;
export const Default: Story = {
render: () => (
<Modal>
<Card.$ paddless borderless>
<Card.Header>
<Card.Title>Hello, world.</Card.Title>
<Card.Description>This is a modal example.</Card.Description>
</Card.Header>
<Card.Body>
<form onSubmit={(event) => event.preventDefault()}>
<Input label="Name" />
<Box className={cx(["py-2"])} />
<Input label="Type" />
<Box className={cx(["py-2"])} />
<Input label="Balance" money />
</form>
</Card.Body>
<Card.Footer className={cx(["flex", "justify-between"])}>
<Button>Cancel</Button>
<Button intent="primary">Save</Button>
</Card.Footer>
</Card.$>
</Modal>
),
};

@ -0,0 +1,27 @@
import { cva } from "class-variance-authority";
const base = [""];
const title = ["text-lg", "font-semibold"];
const description = ["text-mono-text"];
const overlay = ["fixed", "inset-0", "bg-black", "bg-opacity-50"];
const content = [
"fixed",
"top-1/2",
"left-1/2",
"transform",
"-translate-x-1/2",
"-translate-y-1/2",
"bg-transparent",
"w-96",
"rounded-md",
];
const close = ["absolute", "top-3", "right-3", "text-xl"];
const modalStyles = cva(base);
export { modalStyles, title, description, overlay, content, close };

@ -0,0 +1,45 @@
import * as React from "react";
import { FaCircleXmark } from "react-icons/fa6";
import { match, P } from "ts-pattern";
import * as $ from "@radix-ui/react-dialog";
import * as T from "./Modal.types";
import * as styles from "./Modal.styles";
import { Button } from "../Button";
import { cx } from "../../utils";
import { Box } from "../Box";
const Modal = React.forwardRef<T.ModalElement, T.ModalProps>(
({ trigger, children, ...props }, forwardedRef) => {
return (
<$.Root>
<$.Trigger asChild>
{match(trigger)
.with(P.not(P.nullish), (t) => <>{t}</>)
.otherwise(() => (
<Button>Open</Button>
))}
</$.Trigger>
<$.Portal>
<$.Overlay className={cx([styles.overlay])} />
<$.Content
className={cx([styles.content, props.className])}
{...props}
>
<Box>{children}</Box>
<$.Close asChild>
<Button
aria-label="Close"
onlyIcon
className={cx([styles.close, "border-0", "p-0"])}
>
<FaCircleXmark />
</Button>
</$.Close>
</$.Content>
</$.Portal>
</$.Root>
);
}
);
export { Modal };

@ -0,0 +1,13 @@
import React from "react";
import * as $ from "@radix-ui/react-dialog";
import { VariantProps } from "class-variance-authority";
import { modalStyles } from "./Modal.styles";
type ModalElement = React.ElementRef<typeof $.Content>;
type ModalProps = React.ComponentProps<typeof $.Content> &
VariantProps<typeof modalStyles> & {
trigger?: React.ReactNode;
};
export type { ModalElement, ModalProps };

@ -0,0 +1 @@
export * from "./Modal";

@ -0,0 +1,8 @@
import * as React from "react";
import * as $ from "@radix-ui/react-select";
const Select = () => {
return <div>Select</div>;
};
export { Select };

@ -0,0 +1 @@
export * from "./Select";
Loading…
Cancel
Save