feat(core-ui): migrate atoms/molecules/templates from packages/ui
This commit is contained in:
38
packages/core-ui/src/atoms/button/button.stories.tsx
Normal file
38
packages/core-ui/src/atoms/button/button.stories.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { Button } from "./button";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Atoms/Button",
|
||||||
|
component: Button,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: "select",
|
||||||
|
options: ["default", "secondary", "destructive", "outline", "ghost"],
|
||||||
|
},
|
||||||
|
size: { control: "select", options: ["sm", "default", "lg"] },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Button>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: { children: "Button", variant: "default" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Secondary: Story = {
|
||||||
|
args: { children: "Secondary", variant: "secondary" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Destructive: Story = {
|
||||||
|
args: { children: "Destructive", variant: "destructive" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Outline: Story = {
|
||||||
|
args: { children: "Outline", variant: "outline" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Ghost: Story = {
|
||||||
|
args: { children: "Ghost", variant: "ghost" },
|
||||||
|
};
|
||||||
41
packages/core-ui/src/atoms/button/button.tsx
Normal file
41
packages/core-ui/src/atoms/button/button.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { forwardRef, type ButtonHTMLAttributes } from "react";
|
||||||
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||||
|
variant?: "default" | "secondary" | "destructive" | "outline" | "ghost";
|
||||||
|
size?: "sm" | "default" | "lg";
|
||||||
|
}
|
||||||
|
|
||||||
|
const variantStyles: Record<NonNullable<ButtonProps["variant"]>, string> = {
|
||||||
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
|
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeStyles: Record<NonNullable<ButtonProps["size"]>, string> = {
|
||||||
|
sm: "h-9 px-3 text-sm",
|
||||||
|
default: "h-10 px-4 py-2",
|
||||||
|
lg: "h-11 px-8 text-lg",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant = "default", size = "default", ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
variantStyles[variant],
|
||||||
|
sizeStyles[size],
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Button.displayName = "Button";
|
||||||
1
packages/core-ui/src/atoms/button/index.ts
Normal file
1
packages/core-ui/src/atoms/button/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Button, type ButtonProps } from "./button";
|
||||||
3
packages/core-ui/src/atoms/index.ts
Normal file
3
packages/core-ui/src/atoms/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { Button, type ButtonProps } from "./button/index";
|
||||||
|
export { Input, type InputProps } from "./input/index";
|
||||||
|
export { Label, type LabelProps } from "./label/index";
|
||||||
1
packages/core-ui/src/atoms/input/index.ts
Normal file
1
packages/core-ui/src/atoms/input/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Input, type InputProps } from "./input";
|
||||||
19
packages/core-ui/src/atoms/input/input.stories.tsx
Normal file
19
packages/core-ui/src/atoms/input/input.stories.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { Input } from "./input";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Atoms/Input",
|
||||||
|
component: Input,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
} satisfies Meta<typeof Input>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: { placeholder: "Enter text..." },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: { placeholder: "Disabled", disabled: true },
|
||||||
|
};
|
||||||
21
packages/core-ui/src/atoms/input/input.tsx
Normal file
21
packages/core-ui/src/atoms/input/input.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { forwardRef, type InputHTMLAttributes } from "react";
|
||||||
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
|
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Input.displayName = "Input";
|
||||||
1
packages/core-ui/src/atoms/label/index.ts
Normal file
1
packages/core-ui/src/atoms/label/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { Label, type LabelProps } from "./label";
|
||||||
20
packages/core-ui/src/atoms/label/label.tsx
Normal file
20
packages/core-ui/src/atoms/label/label.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { forwardRef, type LabelHTMLAttributes } from "react";
|
||||||
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
|
export interface LabelProps extends LabelHTMLAttributes<HTMLLabelElement> {}
|
||||||
|
|
||||||
|
export const Label = forwardRef<HTMLLabelElement, LabelProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={cn(
|
||||||
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Label.displayName = "Label";
|
||||||
@@ -1 +1,5 @@
|
|||||||
export {};
|
export { cn } from "./lib/utils";
|
||||||
|
export * from "./atoms/index";
|
||||||
|
export * from "./molecules/index";
|
||||||
|
export * from "./organisms/index";
|
||||||
|
export * from "./templates/index";
|
||||||
|
|||||||
6
packages/core-ui/src/lib/utils.ts
Normal file
6
packages/core-ui/src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { clsx, type ClassValue } from "clsx";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs));
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { FormField } from "./form-field";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Molecules/FormField",
|
||||||
|
component: FormField,
|
||||||
|
tags: ["autodocs"],
|
||||||
|
} satisfies Meta<typeof FormField>;
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: { label: "Email", placeholder: "you@example.com", type: "email" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithDescription: Story = {
|
||||||
|
args: {
|
||||||
|
label: "Username",
|
||||||
|
placeholder: "johndoe",
|
||||||
|
description: "Must be 3-31 characters",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithError: Story = {
|
||||||
|
args: {
|
||||||
|
label: "Password",
|
||||||
|
type: "password",
|
||||||
|
error: "Password must be at least 6 characters",
|
||||||
|
},
|
||||||
|
};
|
||||||
31
packages/core-ui/src/molecules/form-field/form-field.tsx
Normal file
31
packages/core-ui/src/molecules/form-field/form-field.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Label } from "../../atoms/label/index";
|
||||||
|
import { Input, type InputProps } from "../../atoms/input/index";
|
||||||
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
|
export interface FormFieldProps extends InputProps {
|
||||||
|
label: string;
|
||||||
|
error?: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FormField({
|
||||||
|
label,
|
||||||
|
error,
|
||||||
|
description,
|
||||||
|
className,
|
||||||
|
id,
|
||||||
|
...inputProps
|
||||||
|
}: FormFieldProps) {
|
||||||
|
const fieldId = id ?? label.toLowerCase().replace(/\s+/g, "-");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("space-y-2", className)}>
|
||||||
|
<Label htmlFor={fieldId}>{label}</Label>
|
||||||
|
<Input id={fieldId} {...inputProps} />
|
||||||
|
{description && (
|
||||||
|
<p className="text-sm text-muted-foreground">{description}</p>
|
||||||
|
)}
|
||||||
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
1
packages/core-ui/src/molecules/form-field/index.ts
Normal file
1
packages/core-ui/src/molecules/form-field/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { FormField, type FormFieldProps } from "./form-field";
|
||||||
1
packages/core-ui/src/molecules/index.ts
Normal file
1
packages/core-ui/src/molecules/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { FormField, type FormFieldProps } from "./form-field/index";
|
||||||
1
packages/core-ui/src/organisms/index.ts
Normal file
1
packages/core-ui/src/organisms/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
26
packages/core-ui/src/styles/globals.css
Normal file
26
packages/core-ui/src/styles/globals.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-background: hsl(0 0% 100%);
|
||||||
|
--color-foreground: hsl(240 10% 3.9%);
|
||||||
|
--color-card: hsl(0 0% 100%);
|
||||||
|
--color-card-foreground: hsl(240 10% 3.9%);
|
||||||
|
--color-popover: hsl(0 0% 100%);
|
||||||
|
--color-popover-foreground: hsl(240 10% 3.9%);
|
||||||
|
--color-primary: hsl(240 5.9% 10%);
|
||||||
|
--color-primary-foreground: hsl(0 0% 98%);
|
||||||
|
--color-secondary: hsl(240 4.8% 95.9%);
|
||||||
|
--color-secondary-foreground: hsl(240 5.9% 10%);
|
||||||
|
--color-muted: hsl(240 4.8% 95.9%);
|
||||||
|
--color-muted-foreground: hsl(240 3.8% 46.1%);
|
||||||
|
--color-accent: hsl(240 4.8% 95.9%);
|
||||||
|
--color-accent-foreground: hsl(240 5.9% 10%);
|
||||||
|
--color-destructive: hsl(0 84.2% 60.2%);
|
||||||
|
--color-destructive-foreground: hsl(0 0% 98%);
|
||||||
|
--color-border: hsl(240 5.9% 90%);
|
||||||
|
--color-input: hsl(240 5.9% 90%);
|
||||||
|
--color-ring: hsl(240 5.9% 10%);
|
||||||
|
--radius-sm: 0.25rem;
|
||||||
|
--radius-md: 0.375rem;
|
||||||
|
--radius-lg: 0.5rem;
|
||||||
|
}
|
||||||
1
packages/core-ui/src/templates/index.ts
Normal file
1
packages/core-ui/src/templates/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
||||||
@@ -7,5 +7,5 @@
|
|||||||
"jsx": "preserve"
|
"jsx": "preserve"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.stories.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user