From 203605b4f0143732564a0eefae9cc6f5cb054583 Mon Sep 17 00:00:00 2001 From: andyv09 Date: Thu, 16 Jan 2025 18:06:54 +0100 Subject: [PATCH] feat: add Checkbox & Label --- .gitignore | 3 + .storybook/preview-head.html | 1 + package.json | 3 + pnpm-lock.yaml | 92 +++++++++++++++++++++++ src/App.tsx | 22 +++--- src/components/Button/index.tsx | 88 +++++++++++----------- src/components/Checkbox/index.stories.tsx | 66 ++++++++++++++++ src/components/Checkbox/index.tsx | 61 +++++++++++++++ src/components/Label/index.stories.tsx | 20 +++++ src/components/Label/index.tsx | 26 +++++++ src/components/Switch/index.stories.tsx | 9 +++ src/components/Switch/index.tsx | 2 +- src/main.tsx | 20 ++--- tailwind.config.js | 80 ++++++++++---------- 14 files changed, 391 insertions(+), 102 deletions(-) create mode 100644 .storybook/preview-head.html create mode 100644 src/components/Checkbox/index.stories.tsx create mode 100644 src/components/Checkbox/index.tsx create mode 100644 src/components/Label/index.stories.tsx create mode 100644 src/components/Label/index.tsx diff --git a/.gitignore b/.gitignore index a547bf3..06a920b 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ dist-ssr *.njsproj *.sln *.sw? + +# Storybook +storybook-static/* \ No newline at end of file diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000..ceea8ce --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index fcf1a70..9c4ad7d 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,14 @@ }, "dependencies": { "@biomejs/biome": "^1.9.4", + "@radix-ui/react-checkbox": "^1.1.3", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-switch": "^1.1.2", "@types/node": "^22.10.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^0.471.1", "react": "^18.3.1", "react-dom": "^18.3.1", "tailwind-merge": "^2.6.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aef401e..656541e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ importers: '@biomejs/biome': specifier: ^1.9.4 version: 1.9.4 + '@radix-ui/react-checkbox': + specifier: ^1.1.3 + version: 1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-label': + specifier: ^2.1.1 + version: 2.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.1.1 version: 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -26,6 +32,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + lucide-react: + specifier: ^0.471.1 + version: 0.471.1(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -533,6 +542,19 @@ packages: '@radix-ui/primitive@1.1.1': resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} + '@radix-ui/react-checkbox@1.1.3': + resolution: {integrity: sha512-HD7/ocp8f1B3e6OHygH0n7ZKjONkhciy1Nh0yuBgObqThc3oyx+vuMfFHKAknXRHHWVE9XvXStxJFyjUmB8PIw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-compose-refs@1.1.1': resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} peerDependencies: @@ -551,6 +573,32 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.1': + resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.2': + resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.0.1': resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} peerDependencies: @@ -1669,6 +1717,11 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@0.471.1: + resolution: {integrity: sha512-syOxwPhf62gg2YOsz72HRn+CIpeudFy67AeKnSR8Hn/fIIF4ubhNbRF+pQ2CaJrl+X9Os4PL87z2DXQi3DVeDA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -2611,6 +2664,22 @@ snapshots: '@radix-ui/primitive@1.1.1': {} + '@radix-ui/react-checkbox@1.1.3(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.0(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-compose-refs@1.1.1(@types/react@18.3.18)(react@18.3.1)': dependencies: react: 18.3.1 @@ -2623,6 +2692,25 @@ snapshots: optionalDependencies: '@types/react': 18.3.18 + '@radix-ui/react-label@2.1.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + + '@radix-ui/react-presence@1.1.2(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.18)(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.3.18)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.18 + '@types/react-dom': 18.3.5(@types/react@18.3.18) + '@radix-ui/react-primitive@2.0.1(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@radix-ui/react-slot': 1.1.1(@types/react@18.3.18)(react@18.3.1) @@ -3790,6 +3878,10 @@ snapshots: dependencies: yallist: 3.1.1 + lucide-react@0.471.1(react@18.3.1): + dependencies: + react: 18.3.1 + lz-string@1.5.0: {} magic-string@0.27.0: diff --git a/src/App.tsx b/src/App.tsx index a205afb..f98962a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,18 @@ -import { useState } from 'react'; -import { Button } from './components'; +import { useState } from "react"; +import { Checkbox } from "./components/Checkbox"; +import { Label } from "./components/Label"; function App() { - const [count, setCount] = useState(0); + const [count, setCount] = useState(0); - return ( - <> - - - ); + return ( +
+
+ + +
+
+ ); } export default App; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 01f33f4..e095dcf 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,55 +1,55 @@ -import { cn } from '@/utils'; -import { Slot } from '@radix-ui/react-slot'; -import { cva, VariantProps } from 'class-variance-authority'; -import React from 'react'; +import { cn } from "@/utils"; +import { Slot } from "@radix-ui/react-slot"; +import { VariantProps, cva } from "class-variance-authority"; +import React from "react"; const buttonVariants = cva( - 'inline-flex items-center justify-center gap-x-4 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', - { - variants: { - variant: { - default: 'bg-monad-purple text-white', - secondary: 'bg-berry text-white', - outline: 'bg-transparent text-monad-purple border border-monad-purple', - }, - size: { - default: 'h-[45px] px-6', - sm: 'h-[35px] px-4 text-sm', - lg: 'h-[55px] px-8 text-lg', - icon: 'h-10 w-10', - }, - radius: { - default: 'rounded-md', - full: 'rounded-full', - none: 'rounded-none', - }, - }, - defaultVariants: { - variant: 'default', - size: 'default', - radius: 'default', - }, - } + "inline-flex items-center justify-center gap-x-4 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-white", + secondary: "bg-berry text-white", + outline: "bg-transparent text-primary border border-primary", + }, + size: { + default: "h-[45px] px-6", + sm: "h-[35px] px-4 text-sm", + lg: "h-[55px] px-8 text-lg", + icon: "h-10 w-10", + }, + radius: { + default: "rounded-md", + full: "rounded-full", + none: "rounded-none", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + radius: "default", + }, + }, ); export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean; + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; } const Button = React.forwardRef( - ({ className, variant, size, radius, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : 'button'; - return ( - - ); - } + ({ className, variant, size, radius, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, ); -Button.displayName = 'Button'; +Button.displayName = "Button"; export { Button, buttonVariants }; diff --git a/src/components/Checkbox/index.stories.tsx b/src/components/Checkbox/index.stories.tsx new file mode 100644 index 0000000..e4cae4c --- /dev/null +++ b/src/components/Checkbox/index.stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Checkbox } from "."; +import { Label } from "../Label"; + +const meta: Meta = { + title: "Components/Checkbox", + component: Checkbox, + parameters: { + layout: "centered", + }, + argTypes: { + size: { + options: ["md", "sm"], + control: { type: "radio" }, + }, + disabled: { + control: { type: "boolean" }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (args) => ( +
+ + +
+ ), + args: { + size: "md", + }, +}; + +export const Sizes: Story = { + render: (args) => ( +
+
+ + +
+
+ + +
+
+ ), + args: { + size: "md", + }, +}; diff --git a/src/components/Checkbox/index.tsx b/src/components/Checkbox/index.tsx new file mode 100644 index 0000000..0e8c0c5 --- /dev/null +++ b/src/components/Checkbox/index.tsx @@ -0,0 +1,61 @@ +"use client"; + +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Check } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/utils"; +import { VariantProps, cva } from "class-variance-authority"; + +const checkboxVariants = cva( + "peer shrink-0 rounded-[4px] border-2 border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-white", + { + variants: { + size: { + sm: "h-[20px] w-[20px]", + md: "h-[30px] w-[30px]", + }, + }, + defaultVariants: { + size: "md", + }, + }, +); + +const checkboxIndicatorVariants = cva("", { + variants: { + size: { + sm: "h-4 w-4", + md: "h-6 w-6", + }, + }, + defaultVariants: { + size: "md", + }, +}); + +export interface CheckboxProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const Checkbox = React.forwardRef< + React.ElementRef, + CheckboxProps +>(({ className, size, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox, checkboxVariants }; diff --git a/src/components/Label/index.stories.tsx b/src/components/Label/index.stories.tsx new file mode 100644 index 0000000..4a0a6eb --- /dev/null +++ b/src/components/Label/index.stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { Label } from "."; + +const meta: Meta = { + title: "Components/Label", + component: Label, + parameters: { + layout: "centered", + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + children: "Custom Label", + }, +}; diff --git a/src/components/Label/index.tsx b/src/components/Label/index.tsx new file mode 100644 index 0000000..3cf5b2a --- /dev/null +++ b/src/components/Label/index.tsx @@ -0,0 +1,26 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { type VariantProps, cva } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/utils"; + +const labelVariants = cva( + "text-md text-gray-60 font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label, labelVariants }; diff --git a/src/components/Switch/index.stories.tsx b/src/components/Switch/index.stories.tsx index 8d1448a..592cc97 100644 --- a/src/components/Switch/index.stories.tsx +++ b/src/components/Switch/index.stories.tsx @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { Switch } from "."; +import { Label } from "../Label"; const meta: Meta = { title: "Components/Switch", @@ -23,6 +24,14 @@ export default meta; type Story = StoryObj; export const Default: Story = { + render: (args) => ( +
+ + +
+ ), args: { size: "md", }, diff --git a/src/components/Switch/index.tsx b/src/components/Switch/index.tsx index 55ebf8f..e610c5f 100644 --- a/src/components/Switch/index.tsx +++ b/src/components/Switch/index.tsx @@ -6,7 +6,7 @@ import { VariantProps, cva } from "class-variance-authority"; import * as React from "react"; const switchVariants = cva( - "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed data-[state=checked]:bg-monad-purple data-[state=unchecked]:bg-gray-80 disabled:opacity-50", + "peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed data-[state=checked]:bg-primary data-[state=unchecked]:bg-gray-80 disabled:opacity-50", { variants: { size: { diff --git a/src/main.tsx b/src/main.tsx index bef5202..e8567e6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,12 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; -createRoot(document.getElementById('root')!).render( - - - , -) +createRoot(document.getElementById("root")!).render( + +
+ +
+
, +); diff --git a/tailwind.config.js b/tailwind.config.js index b6db510..ecd14e2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,41 +1,45 @@ /** @type {import('tailwindcss').Config} */ export default { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], - darkMode: ['class', '[data-mode="dark"]'], - theme: { - extend: { - colors: { - 'monad-purple': '#836EF9', - 'monad-dark-blue': '#200052', - 'electric-ice': '#5FEDDF', - berry: '#A0055D', - wine: '#61004F', - 'purple-light-25': '#BBAFFF', - 'purple-light-50': '#9E8CFF', - 'purple-dark-25': '#632ADB', - 'purple-dark-50': '#3C1B84', - 'blue-light-25': '#9F90F9', - 'blue-light-50': '#8975FF', - 'blue-dark-25': '#563AFD', - 'blue-dark-50': '#1A0E62', - black: '#000000', - 'black-80': '#232323', - 'black-60': '#4D4D4D', - 'black-40': '#7A7A7A', - white: '#ffffff', - 'white-80': '#F1F1F1', - 'white-60': '#E2E2E2', - 'white-40': '#D6D6D6', - gray: '#1F2937', - 'gray-80': '#374151', - 'gray-60': '#4B5563', - 'gray-40': '#6B7280', - info: '#A0C3FF', - success: '#2CA77B', - warning: '#FBC756', - error: '#F60F0F', - }, - }, - }, - plugins: [], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + darkMode: ["class", '[data-mode="dark"]'], + theme: { + extend: { + colors: { + primary: "#836EF9", + secondary: "#200052", + "electric-ice": "#5FEDDF", + berry: "#A0055D", + wine: "#61004F", + "primary-light-25": "#BBAFFF", + "primary-light-50": "#9E8CFF", + "primary-dark-25": "#632ADB", + "primary-dark-50": "#3C1B84", + "secondary-light-25": "#9F90F9", + "secondary-light-50": "#8975FF", + "secondary-dark-25": "#563AFD", + "secondary-dark-50": "#1A0E62", + black: "#000000", + "black-80": "#232323", + "black-60": "#4D4D4D", + "black-40": "#7A7A7A", + white: "#ffffff", + "white-80": "#F1F1F1", + "white-60": "#E2E2E2", + "white-40": "#D6D6D6", + gray: "#1F2937", + "gray-80": "#374151", + "gray-60": "#4B5563", + "gray-40": "#6B7280", + info: "#A0C3FF", + success: "#2CA77B", + warning: "#FBC756", + error: "#F60F0F", + }, + fontFamily: { + sans: ["Ubuntu", "sans-serif"], // Default font + inter: ["Inter", "sans-serif"], // Secondary font + }, + }, + }, + plugins: [], };