| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Button } from "."; | ||
| const meta: Meta<typeof Button> = { | ||
| title: "Components/Button", | ||
| component: Button, | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| tags: ["autodocs"], | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
| export const Solid: Story = { | ||
| args: { | ||
| variant: "solid", | ||
| children: "Button", | ||
| }, | ||
| }; | ||
| export const Outline: Story = { | ||
| args: { | ||
| variant: "outline", | ||
| children: "Button", | ||
| }, | ||
| }; | ||
| export const Ghost: Story = { | ||
| args: { | ||
| variant: "ghost", | ||
| children: "Button", | ||
| }, | ||
| }; |
| import { cn } from "@/utils"; | ||
| import { cva, VariantProps } from "class-variance-authority"; | ||
| import { ComponentProps, forwardRef } from "react"; | ||
| const buttonStyles = cva( | ||
| [ | ||
| "w-full", | ||
| "rounded-md", | ||
| "font-semibold", | ||
| "focus:outline-none", | ||
| "disabled:cursor-not-allowed", | ||
| ], | ||
| { | ||
| variants: { | ||
| variant: { | ||
| solid: "", | ||
| outline: "border-2", | ||
| ghost: "transition-colors duration-300", | ||
| }, | ||
| size: { | ||
| sm: "px-4 py-2 text-sm", | ||
| md: "px-4 py-2 text-base", | ||
| lg: "px-6 py-3 text-lg", | ||
| }, | ||
| colorscheme: { | ||
| primary: "text-white", | ||
| }, | ||
| }, | ||
| compoundVariants: [ | ||
| { | ||
| variant: "solid", | ||
| colorscheme: "primary", | ||
| className: "bg-primary-500 hover:bg-primary-600", | ||
| }, | ||
| { | ||
| variant: "outline", | ||
| colorscheme: "primary", | ||
| className: | ||
| "text-primary-600 border-primary-500 bg-transparent hover:bg-primary-100", | ||
| }, | ||
| { | ||
| variant: "ghost", | ||
| colorscheme: "primary", | ||
| className: "text-primary-600 bg-transparent hover:bg-primary-100", | ||
| }, | ||
| ], | ||
| defaultVariants: { | ||
| variant: "solid", | ||
| size: "md", | ||
| colorscheme: "primary", | ||
| }, | ||
| } | ||
| ); | ||
| type ButtonProps = ComponentProps<"button"> & VariantProps<typeof buttonStyles>; | ||
| export const Button = forwardRef<HTMLButtonElement, ButtonProps>( | ||
| ({ variant, size, colorscheme, className, ...props }, ref) => { | ||
| return ( | ||
| <button | ||
| ref={ref} | ||
| className={cn(buttonStyles({ variant, size, colorscheme, className }))} | ||
| {...props} | ||
| /> | ||
| ); | ||
| } | ||
| ); |
| export { Animatedtabs } from "./ui/Animatedtabs/animatedtabs"; |
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Input } from "."; | ||
| const meta: Meta<typeof Input> = { | ||
| title: "Components/Input", | ||
| component: Input, | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| tags: ["autodocs"], | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
| export const Text: Story = { | ||
| args: { | ||
| type: "text", | ||
| placeholder: "Insert text here", | ||
| }, | ||
| }; | ||
| export const Password: Story = { | ||
| args: { | ||
| type: "password", | ||
| placeholder: "Password", | ||
| }, | ||
| }; | ||
| export const Number: Story = { | ||
| args: { | ||
| type: "number", | ||
| placeholder: "Number", | ||
| }, | ||
| }; | ||
| export const Date: Story = { | ||
| args: { | ||
| type: "date", | ||
| placeholder: "Date", | ||
| }, | ||
| }; |
| import { cn } from "@/utils"; | ||
| import { cva, VariantProps } from "class-variance-authority"; | ||
| import { ComponentProps, forwardRef } from "react"; | ||
| const inputStyles = cva([ | ||
| "w-full", | ||
| "border", | ||
| "border-gray-200", | ||
| "p-2", | ||
| "rounded-lg", | ||
| "transition-all", | ||
| "duration-100", | ||
| "outline-none", | ||
| "focus:outline-primary-500 ", | ||
| "focus:border-transparent", | ||
| "placeholder:text-gray-400", | ||
| "placeholder:text-sm", | ||
| ]); | ||
| type InputProps = ComponentProps<"input"> & VariantProps<typeof inputStyles>; | ||
| export const Input = forwardRef<HTMLInputElement, InputProps>( | ||
| ({ className, ...props }, ref) => { | ||
| return ( | ||
| <input | ||
| ref={ref} | ||
| type="text" | ||
| autoComplete="off" | ||
| className={cn(inputStyles({ className }))} | ||
| {...props} | ||
| /> | ||
| ); | ||
| } | ||
| ); |
| import { ComponentPropsWithRef, forwardRef } from "react"; | ||
| export type BoxProps = ComponentPropsWithRef<"div">; | ||
| export const Box = forwardRef<HTMLDivElement, BoxProps>(({ ...props }, ref) => { | ||
| return <div ref={ref} {...props} />; | ||
| }); |
| export * from "./Box"; | ||
| export * from "./Stack"; |
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Stack } from "."; | ||
| import { Box } from ".."; | ||
| const meta: Meta<typeof Stack> = { | ||
| title: "Layout/Stack", | ||
| component: Stack, | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
| export const Default: Story = { | ||
| render: (args) => ( | ||
| <Stack className="gap-4 p-4 bg-gray-300" {...args}> | ||
| <Box className="w-[100px] h-[100px] bg-blue-500" /> | ||
| <Box className="w-[100px] h-[100px] bg-red-500" /> | ||
| <Box className="w-[100px] h-[100px] bg-green-500" /> | ||
| </Stack> | ||
| ), | ||
| }; |
| import { Box, BoxProps } from "@/components"; | ||
| import { cn } from "@/utils"; | ||
| type StackProps = BoxProps; | ||
| export const Stack = ({ className, ...props }: StackProps) => { | ||
| return ( | ||
| <Box className={cn("flex flex-col items-start", className)} {...props} /> | ||
| ); | ||
| }; |
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Text } from "."; | ||
| const meta: Meta<typeof Text> = { | ||
| title: "Components/Text", | ||
| component: Text, | ||
| parameters: { | ||
| layout: "centered", | ||
| }, | ||
| tags: ["autodocs"], | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof meta>; | ||
| export const Default: Story = { | ||
| args: { | ||
| as: "h1", | ||
| children: | ||
| "be parts correct potatoes sides donkey extra climate happily freedom relationship tape unit tall hung call cat window steady world front graph particular pick", | ||
| }, | ||
| }; |
| import { cn } from "@/utils"; | ||
| import { | ||
| PolymorphicComponentPropsWithRef, | ||
| PolymorphicRef, | ||
| } from "@/utils/types"; | ||
| import { VariantProps, cva } from "class-variance-authority"; | ||
| import { forwardRef } from "react"; | ||
| const textStyles = cva("w-full", { | ||
| variants: { | ||
| emphasis: { | ||
| low: "text-gray-600 font-light", | ||
| }, | ||
| size: { | ||
| sm: "text-sm", | ||
| base: "text-base", | ||
| lg: "text-lg", | ||
| xl: "text-xl", | ||
| "2xl": "text-2xl", | ||
| "3xl": "text-3xl", | ||
| }, | ||
| weight: { | ||
| thin: "font-thin", | ||
| normal: "font-normal", | ||
| medium: "font-medium", | ||
| semibold: "font-semibold", | ||
| bold: "font-bold", | ||
| black: "font-black", | ||
| }, | ||
| align: { | ||
| left: "text-left", | ||
| center: "text-center", | ||
| right: "text-right", | ||
| }, | ||
| italic: { | ||
| true: "italic", | ||
| }, | ||
| underline: { | ||
| true: "underline underline-offset-2", | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| size: "base", | ||
| align: "left", | ||
| }, | ||
| }); | ||
| type TextProps<C extends React.ElementType> = PolymorphicComponentPropsWithRef< | ||
| C, | ||
| VariantProps<typeof textStyles> | ||
| >; | ||
| type TextComponent = <C extends React.ElementType = "span">( | ||
| props: TextProps<C> | ||
| ) => React.ReactElement | null; | ||
| // @ts-expect-error - unexpected typing errors | ||
| export const Text: TextComponent = forwardRef( | ||
| <C extends React.ElementType = "span">( | ||
| { | ||
| as, | ||
| align, | ||
| size, | ||
| emphasis, | ||
| italic, | ||
| underline, | ||
| weight, | ||
| className, | ||
| ...props | ||
| }: TextProps<C>, | ||
| ref?: PolymorphicRef<C> | ||
| ) => { | ||
| const Component = as || "span"; | ||
| return ( | ||
| <Component | ||
| ref={ref} | ||
| className={cn( | ||
| textStyles({ | ||
| size, | ||
| weight, | ||
| emphasis, | ||
| italic, | ||
| underline, | ||
| align, | ||
| className, | ||
| }) | ||
| )} | ||
| {...props} | ||
| /> | ||
| ); | ||
| } | ||
| ); |
| import type { Meta, StoryObj } from "@storybook/react"; | ||
| import { Animatedtabs, type Tab } from "./animatedtabs"; | ||
| const meta: Meta<typeof Animatedtabs> = { | ||
| title: "Components/AnimatedTabs", | ||
| component: Animatedtabs, | ||
| parameters: { | ||
| layout: "padded", | ||
| docs: { | ||
| description: { | ||
| component: | ||
| "A reusable animated tab component with multiple variants and sizes. Built with Framer Motion for smooth animations.", | ||
| }, | ||
| }, | ||
| }, | ||
| argTypes: { | ||
| variant: { | ||
| control: "select", | ||
| options: ["default", "pills", "underline", "cards"], | ||
| description: "Visual style variant of the tabs", | ||
| }, | ||
| size: { | ||
| control: "select", | ||
| options: ["sm", "md", "lg"], | ||
| description: "Size of the tabs", | ||
| }, | ||
| orientation: { | ||
| control: "select", | ||
| options: ["horizontal", "vertical"], | ||
| description: "Orientation of the tab layout", | ||
| }, | ||
| animated: { | ||
| control: "boolean", | ||
| description: "Enable/disable animations", | ||
| }, | ||
| onTabChange: { | ||
| action: "tab-changed", | ||
| description: "Callback fired when tab changes", | ||
| }, | ||
| }, | ||
| tags: ["autodocs"], | ||
| }; | ||
| export default meta; | ||
| type Story = StoryObj<typeof Animatedtabs>; | ||
| const sampleTabs: Tab[] = [ | ||
| { | ||
| id: "research", | ||
| label: "Research", | ||
| content: ( | ||
| <div className="p-4 rounded-lg bg-muted/50"> | ||
| <h3 className="mb-2 text-lg font-semibold">Research Phase</h3> | ||
| <p className="text-muted-foreground"> | ||
| Gather information, analyze market trends, and understand user needs. | ||
| This phase is crucial for making informed decisions. | ||
| </p> | ||
| </div> | ||
| ), | ||
| }, | ||
| { | ||
| id: "outline", | ||
| label: "Outline", | ||
| content: ( | ||
| <div className="p-4 rounded-lg bg-muted/50"> | ||
| <h3 className="mb-2 text-lg font-semibold">Project Outline</h3> | ||
| <ul className="space-y-1 list-disc list-inside text-muted-foreground"> | ||
| <li>Define project scope and objectives</li> | ||
| <li>Create timeline and milestones</li> | ||
| <li>Identify resources and constraints</li> | ||
| <li>Plan deliverables and success metrics</li> | ||
| </ul> | ||
| </div> | ||
| ), | ||
| }, | ||
| { | ||
| id: "script", | ||
| label: "Script", | ||
| content: ( | ||
| <div className="p-4 rounded-lg bg-muted/50"> | ||
| <h3 className="mb-2 text-lg font-semibold">Script Development</h3> | ||
| <p className="text-muted-foreground"> | ||
| Write detailed scripts, create user flows, and document technical | ||
| specifications. This ensures smooth execution. | ||
| </p> | ||
| </div> | ||
| ), | ||
| }, | ||
| ]; | ||
| const longTabList: Tab[] = [ | ||
| { | ||
| id: "overview", | ||
| label: "Overview", | ||
| content: <div className="p-4">Overview content</div>, | ||
| }, | ||
| { | ||
| id: "features", | ||
| label: "Features", | ||
| content: <div className="p-4">Features content</div>, | ||
| }, | ||
| { | ||
| id: "pricing", | ||
| label: "Pricing", | ||
| content: <div className="p-4">Pricing content</div>, | ||
| }, | ||
| { | ||
| id: "documentation", | ||
| label: "Documentation", | ||
| content: <div className="p-4">Documentation content</div>, | ||
| }, | ||
| { | ||
| id: "support", | ||
| label: "Support", | ||
| content: <div className="p-4">Support content</div>, | ||
| }, | ||
| { | ||
| id: "changelog", | ||
| label: "Changelog", | ||
| content: <div className="p-4">Changelog content</div>, | ||
| }, | ||
| ]; | ||
| const tabsWithDisabled: Tab[] = [ | ||
| { | ||
| id: "active1", | ||
| label: "Active Tab", | ||
| content: <div className="p-4">This tab is active</div>, | ||
| }, | ||
| { | ||
| id: "active2", | ||
| label: "Another Active", | ||
| content: <div className="p-4">This tab is also active</div>, | ||
| }, | ||
| { | ||
| id: "disabled", | ||
| label: "Disabled Tab", | ||
| content: <div className="p-4">This content won't show</div>, | ||
| disabled: true, | ||
| }, | ||
| { | ||
| id: "active3", | ||
| label: "Last Active", | ||
| content: <div className="p-4">This is the last active tab</div>, | ||
| }, | ||
| ]; | ||
| // Default story | ||
| export const Default: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "default", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| // Variant stories | ||
| export const Pills: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "pills", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| export const Underline: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "underline", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| export const Cards: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "cards", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| // Size stories | ||
| export const Small: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "pills", | ||
| size: "sm", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| export const Large: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "pills", | ||
| size: "lg", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| // Orientation stories | ||
| export const Vertical: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "default", | ||
| size: "md", | ||
| orientation: "vertical", | ||
| animated: true, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: | ||
| "Vertical orientation places tabs in a column layout, useful for sidebar navigation.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| export const VerticalPills: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "pills", | ||
| size: "md", | ||
| orientation: "vertical", | ||
| animated: true, | ||
| }, | ||
| }; | ||
| // Special cases | ||
| export const WithoutAnimation: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "default", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: false, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: | ||
| "Tabs without animation for better performance or accessibility needs.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| export const ManyTabs: Story = { | ||
| args: { | ||
| tabs: longTabList, | ||
| defaultTab: "overview", | ||
| variant: "underline", | ||
| size: "sm", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: "Example with many tabs to test overflow behavior.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| export const WithDisabledTabs: Story = { | ||
| args: { | ||
| tabs: tabsWithDisabled, | ||
| defaultTab: "active1", | ||
| variant: "pills", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: "Tabs with some disabled options that cannot be selected.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| export const TabsOnly: Story = { | ||
| args: { | ||
| tabs: [ | ||
| { id: "tab1", label: "Tab 1" }, | ||
| { id: "tab2", label: "Tab 2" }, | ||
| { id: "tab3", label: "Tab 3" }, | ||
| ], | ||
| defaultTab: "tab1", | ||
| variant: "underline", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: | ||
| "Tabs without content panels, useful for navigation-only scenarios.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| // Custom styling story | ||
| export const CustomStyling: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "pills", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| className: "bg-slate-100 p-4 rounded-xl", | ||
| tabClassName: "font-bold", | ||
| contentClassName: "mt-6 p-6 bg-white rounded-lg shadow-sm", | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: "Example with custom styling applied via className props.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; | ||
| // Interactive playground | ||
| export const Playground: Story = { | ||
| args: { | ||
| tabs: sampleTabs, | ||
| defaultTab: "research", | ||
| variant: "default", | ||
| size: "md", | ||
| orientation: "horizontal", | ||
| animated: true, | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: | ||
| "Interactive playground to test different combinations of props.", | ||
| }, | ||
| }, | ||
| }, | ||
| }; |
| "use client"; | ||
| import { cn } from "@/lib/utils"; | ||
| import { motion } from "framer-motion"; | ||
| import * as React from "react"; | ||
| export interface Tab { | ||
| id: string; | ||
| label: string; | ||
| content?: React.ReactNode; | ||
| disabled?: boolean; | ||
| } | ||
| export interface AnimatedTabsProps { | ||
| tabs: Tab[]; | ||
| defaultTab?: string; | ||
| onTabChange?: (tabId: string) => void; | ||
| variant?: "default" | "pills" | "underline" | "cards"; | ||
| size?: "sm" | "md" | "lg"; | ||
| orientation?: "horizontal" | "vertical"; | ||
| className?: string; | ||
| tabClassName?: string; | ||
| contentClassName?: string; | ||
| animated?: boolean; | ||
| } | ||
| const AnimatedTabs = React.forwardRef<HTMLDivElement, AnimatedTabsProps>( | ||
| ( | ||
| { | ||
| tabs, | ||
| defaultTab, | ||
| onTabChange, | ||
| variant = "default", | ||
| size = "md", | ||
| orientation = "horizontal", | ||
| className, | ||
| tabClassName, | ||
| contentClassName, | ||
| animated = true, | ||
| ...props | ||
| }, | ||
| ref | ||
| ) => { | ||
| const [activeTab, setActiveTab] = React.useState(defaultTab || tabs[0]?.id); | ||
| const handleTabChange = (tabId: string) => { | ||
| const tab = tabs.find((t) => t.id === tabId); | ||
| if (tab && !tab.disabled) { | ||
| setActiveTab(tabId); | ||
| onTabChange?.(tabId); | ||
| } | ||
| }; | ||
| const activeTabContent = tabs.find((tab) => tab.id === activeTab)?.content; | ||
| const tabListVariants = { | ||
| default: "border-b border-muted", | ||
| pills: "bg-muted p-1 rounded-lg", | ||
| underline: "border-b border-border", | ||
| cards: "gap-2", | ||
| }; | ||
| const tabVariants = { | ||
| default: { | ||
| base: "relative px-4 py-2 text-sm font-medium transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50", | ||
| active: "text-foreground", | ||
| inactive: "text-muted-foreground", | ||
| }, | ||
| pills: { | ||
| base: "relative px-3 py-1.5 text-sm font-medium transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50 rounded-md", | ||
| active: "text-foreground", | ||
| inactive: "text-muted-foreground", | ||
| }, | ||
| underline: { | ||
| base: "relative px-4 py-2 text-sm font-medium transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50", | ||
| active: "text-foreground", | ||
| inactive: "text-muted-foreground", | ||
| }, | ||
| cards: { | ||
| base: "relative px-4 py-2 text-sm font-medium transition-colors hover:text-foreground disabled:pointer-events-none disabled:opacity-50 rounded-lg border", | ||
| active: "text-foreground bg-background border-border shadow-sm", | ||
| inactive: | ||
| "text-muted-foreground border-transparent hover:border-border", | ||
| }, | ||
| }; | ||
| const sizeVariants = { | ||
| sm: "text-xs px-2 py-1", | ||
| md: "text-sm px-4 py-2", | ||
| lg: "text-base px-6 py-3", | ||
| }; | ||
| const getIndicatorProps = () => { | ||
| switch (variant) { | ||
| case "pills": | ||
| return { | ||
| className: "absolute inset-0 bg-background rounded-md shadow-sm", | ||
| layoutId: "pill-indicator", | ||
| }; | ||
| case "underline": | ||
| return { | ||
| className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary", | ||
| layoutId: "underline-indicator", | ||
| }; | ||
| default: | ||
| return { | ||
| className: "absolute bottom-0 left-0 right-0 h-0.5 bg-primary", | ||
| layoutId: "default-indicator", | ||
| }; | ||
| } | ||
| }; | ||
| return ( | ||
| <div | ||
| ref={ref} | ||
| className={cn( | ||
| "w-full", | ||
| orientation === "vertical" && "flex gap-4", | ||
| className | ||
| )} | ||
| {...props} | ||
| > | ||
| <div | ||
| className={cn( | ||
| "flex", | ||
| orientation === "horizontal" ? "flex-row" : "flex-col", | ||
| orientation === "vertical" && "min-w-[200px]", | ||
| tabListVariants[variant] | ||
| )} | ||
| role="tablist" | ||
| > | ||
| {tabs.map((tab) => ( | ||
| <button | ||
| key={tab.id} | ||
| role="tab" | ||
| aria-selected={activeTab === tab.id} | ||
| aria-controls={`panel-${tab.id}`} | ||
| disabled={tab.disabled} | ||
| className={cn( | ||
| tabVariants[variant].base, | ||
| sizeVariants[size], | ||
| activeTab === tab.id | ||
| ? tabVariants[variant].active | ||
| : tabVariants[variant].inactive, | ||
| tabClassName | ||
| )} | ||
| onClick={() => handleTabChange(tab.id)} | ||
| > | ||
| {animated && activeTab === tab.id && variant !== "cards" && ( | ||
| <motion.div | ||
| {...getIndicatorProps()} | ||
| transition={{ type: "spring", bounce: 0.2, duration: 0.6 }} | ||
| /> | ||
| )} | ||
| <span className="relative z-10">{tab.label}</span> | ||
| </button> | ||
| ))} | ||
| </div> | ||
| {activeTabContent && ( | ||
| <div | ||
| className={cn( | ||
| "mt-4", | ||
| orientation === "vertical" && "mt-0 flex-1", | ||
| contentClassName | ||
| )} | ||
| > | ||
| {animated ? ( | ||
| <motion.div | ||
| key={activeTab} | ||
| initial={{ opacity: 0, y: 10 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0, y: -10 }} | ||
| transition={{ duration: 0.2 }} | ||
| role="tabpanel" | ||
| id={`panel-${activeTab}`} | ||
| > | ||
| {activeTabContent} | ||
| </motion.div> | ||
| ) : ( | ||
| <div role="tabpanel" id={`panel-${activeTab}`}> | ||
| {activeTabContent} | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| ); | ||
| AnimatedTabs.displayName = "AnimatedTabs"; | ||
| export { AnimatedTabs }; |
| import * as React from "react" | ||
| import { Slot } from "@radix-ui/react-slot" | ||
| import { cva, type VariantProps } from "class-variance-authority" | ||
| import { cn } from "@/lib/utils" | ||
| const buttonVariants = cva( | ||
| "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", | ||
| { | ||
| variants: { | ||
| variant: { | ||
| default: | ||
| "bg-primary text-primary-foreground shadow hover:bg-primary/90", | ||
| destructive: | ||
| "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", | ||
| outline: | ||
| "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", | ||
| secondary: | ||
| "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", | ||
| ghost: "hover:bg-accent hover:text-accent-foreground", | ||
| link: "text-primary underline-offset-4 hover:underline", | ||
| }, | ||
| size: { | ||
| default: "h-9 px-4 py-2", | ||
| sm: "h-8 rounded-md px-3 text-xs", | ||
| lg: "h-10 rounded-md px-8", | ||
| icon: "h-9 w-9", | ||
| }, | ||
| }, | ||
| defaultVariants: { | ||
| variant: "default", | ||
| size: "default", | ||
| }, | ||
| } | ||
| ) | ||
| export interface ButtonProps | ||
| extends React.ButtonHTMLAttributes<HTMLButtonElement>, | ||
| VariantProps<typeof buttonVariants> { | ||
| asChild?: boolean | ||
| } | ||
| const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||
| ({ className, variant, size, asChild = false, ...props }, ref) => { | ||
| const Comp = asChild ? Slot : "button" | ||
| return ( | ||
| <Comp | ||
| className={cn(buttonVariants({ variant, size, className }))} | ||
| ref={ref} | ||
| {...props} | ||
| /> | ||
| ) | ||
| } | ||
| ) | ||
| Button.displayName = "Button" | ||
| export { Button, buttonVariants } |
| export declare function addComponent(name: string): Promise<void>; |
| export declare function initProject(): Promise<void>; |
| #!/usr/bin/env node | ||
| export {}; |
+20
-9
@@ -10,17 +10,28 @@ import chalk from "chalk"; | ||
| } | ||
| function toKebabCase(str) { | ||
| return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(); | ||
| } | ||
| export async function addComponent(name) { | ||
| const folderName = toPascalCase(name); // e.g. Animatedtabs | ||
| const fileName = `${folderName}.tsx`; // e.g. Animatedtabs.tsx | ||
| const sourceFile = path.resolve(__dirname, "../../src/components/ui", folderName, fileName); | ||
| const targetDir = path.resolve(process.cwd(), "components/ui", folderName); | ||
| // Support both kebab-case and PascalCase input | ||
| const kebabName = toKebabCase(name); // e.g. animated-tabs | ||
| const pascalName = toPascalCase(kebabName); // e.g. AnimatedTabs | ||
| const fileName = `${pascalName}.tsx`; // e.g. AnimatedTabs.tsx | ||
| // Look for the component in the CLI's components directory | ||
| const sourceFile = path.resolve(__dirname, "../../components/ui", pascalName, fileName); | ||
| const targetDir = path.resolve(process.cwd(), "components/ui", pascalName); | ||
| const targetFile = path.join(targetDir, fileName); | ||
| const exists = await fs.pathExists(sourceFile); | ||
| if (!exists) { | ||
| console.log(chalk.red(`❌ Component not found.\nExpected at: ${sourceFile}\nMake sure the component folder and file follow PascalCase.`)); | ||
| console.log(chalk.red(`❌ Component '${name}' not found.\nExpected at: ${sourceFile}\nMake sure the component exists in the CLI package.`)); | ||
| return; | ||
| } | ||
| await fs.ensureDir(targetDir); | ||
| await fs.copyFile(sourceFile, targetFile); | ||
| console.log(chalk.green(`✅ ${fileName} copied to components/ui/${folderName}/`)); | ||
| console.log(chalk.yellow(`👉 Import using: import { AnimatedTabs } from "@/components/ui/Animatedtabs/Animatedtabs";`)); | ||
| try { | ||
| await fs.ensureDir(targetDir); | ||
| await fs.copyFile(sourceFile, targetFile); | ||
| console.log(chalk.green(`✅ ${fileName} copied to components/ui/${pascalName}/`)); | ||
| console.log(chalk.yellow(`👉 Import using: import { ${pascalName} } from "@/components/ui/${pascalName}/${pascalName}";`)); | ||
| } | ||
| catch (error) { | ||
| console.error(chalk.red("❌ Error copying component:"), error); | ||
| } | ||
| } |
+12
-2
| #!/usr/bin/env node | ||
| import { Command } from "commander"; | ||
| import { addComponent } from "./commands/add.js"; | ||
| import { initProject } from "./commands/init.js"; | ||
| const program = new Command(); | ||
| program.name("zedblock").description("ZedBlock UI CLI").version("0.0.1"); | ||
| program | ||
| .name("zedblock") | ||
| .description("ZedBlock UI CLI - Add beautiful UI components to your project") | ||
| .version("0.0.8"); | ||
| program | ||
| .command("add") | ||
| .description("Add a component to your project") | ||
| .argument("<component>", "Component name") | ||
| .argument("<component>", "Component name (e.g., animated-tabs)") | ||
| .action(async (component) => { | ||
| await addComponent(component); | ||
| }); | ||
| program | ||
| .command("init") | ||
| .description("Initialize ZedBlock UI in your project") | ||
| .action(async () => { | ||
| await initProject(); | ||
| }); | ||
| program.parse(); |
+25
-4
| { | ||
| "name": "zedblock", | ||
| "version": "0.0.8", | ||
| "version": "0.0.9", | ||
| "type": "module", | ||
@@ -9,6 +9,9 @@ "bin": { | ||
| "scripts": { | ||
| "build": "tsc" | ||
| "build": "node build.js && tsc", | ||
| "dev": "tsc --watch", | ||
| "prepublishOnly": "npm run build" | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| "dist", | ||
| "components" | ||
| ], | ||
@@ -21,4 +24,22 @@ "dependencies": { | ||
| "devDependencies": { | ||
| "@types/fs-extra": "^11.0.4", | ||
| "@types/node": "^20.0.0", | ||
| "typescript": "^5.2.2" | ||
| } | ||
| }, | ||
| "engines": { | ||
| "node": ">=18.0.0" | ||
| }, | ||
| "keywords": [ | ||
| "ui", | ||
| "components", | ||
| "react", | ||
| "tailwind", | ||
| "cli" | ||
| ], | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "your-repo-url" | ||
| }, | ||
| "author": "Your Name", | ||
| "license": "MIT" | ||
| } |
| import chalk from "chalk"; | ||
| import fs from "fs-extra"; | ||
| /** | ||
| * Convert `animated-tabs` to `AnimatedTabs` | ||
| */ | ||
| export const toPascalCase = (str) => str.replace(/(^\w|-\w)/g, (match) => match.replace("-", "").toUpperCase()); | ||
| /** | ||
| * Check if a directory exists | ||
| */ | ||
| export async function exists(path) { | ||
| return fs.pathExists(path); | ||
| } | ||
| /** | ||
| * Copy a folder from source to target | ||
| */ | ||
| export async function copyComponent(source, target) { | ||
| try { | ||
| await fs.ensureDir(target); | ||
| await fs.copy(source, target, { overwrite: true }); | ||
| console.log(chalk.green(`✅ Copied to ${target}`)); | ||
| } | ||
| catch (err) { | ||
| console.error(chalk.red("❌ Error copying component:"), err); | ||
| } | ||
| } |
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
Unpublished package
Supply chain riskPackage version was not found on the registry. It may exist on a different registry and need to be configured to pull from that registry.
No contributors or author data
MaintenancePackage does not specify a list of contributors or an author in package.json.
No License Found
LicenseLicense information could not be found.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
26651
736.77%21
320%0
-100%962
1165.79%1
-50%0
-100%3
200%