ui-arreya-components 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,138 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react"
2
- import { GraphCard } from "./graph-card"
3
- import { Area, AreaChart, ResponsiveContainer } from "recharts"
4
-
5
- const meta: Meta<typeof GraphCard> = {
6
- title: "Feature/GraphCard",
7
- component: GraphCard,
8
- parameters: {
9
- layout: "centered",
10
- },
11
- tags: ["autodocs"],
12
- argTypes: {
13
- title: {
14
- control: "text",
15
- description: "The title of the graph card",
16
- },
17
- description: {
18
- control: "text",
19
- description: "Optional description text below the title",
20
- },
21
- value: {
22
- control: "text",
23
- description: "The primary value to display (e.g., total, average)",
24
- },
25
- change: {
26
- control: "number",
27
- description: "Percentage change to display",
28
- },
29
- changeType: {
30
- control: { type: "select" },
31
- options: ["increase", "decrease", "neutral"],
32
- description: "Type of change (affects color and icon)",
33
- },
34
- changeLabel: {
35
- control: "text",
36
- description: "Label to display after the change value",
37
- },
38
- isLoading: {
39
- control: "boolean",
40
- description: "Whether to show a loading state",
41
- },
42
- },
43
- }
44
-
45
- export default meta
46
- type Story = StoryObj<typeof GraphCard>
47
-
48
- // Simple example with minimal data
49
- export const Simple: Story = {
50
- args: {
51
- title: "Monthly Sales",
52
- description: "Total sales for the current month",
53
- value: "$12,345",
54
- chart: (
55
- <ResponsiveContainer width="100%" height="100%">
56
- <AreaChart
57
- data={[
58
- { month: "Jan", value: 1000 },
59
- { month: "Feb", value: 2000 },
60
- { month: "Mar", value: 1500 },
61
- { month: "Apr", value: 3000 },
62
- { month: "May", value: 2500 },
63
- { month: "Jun", value: 4000 },
64
- ]}
65
- >
66
- <Area type="monotone" dataKey="value" stroke="hsl(var(--primary))" fill="hsl(var(--primary) / 0.2)" />
67
- </AreaChart>
68
- </ResponsiveContainer>
69
- ),
70
- },
71
- }
72
-
73
- // Example with positive change
74
- export const PositiveChange: Story = {
75
- args: {
76
- title: "Website Traffic",
77
- description: "Unique visitors per day",
78
- value: "12,456",
79
- change: 23.5,
80
- changeType: "increase",
81
- changeLabel: "vs last week",
82
- chart: (
83
- <ResponsiveContainer width="100%" height="100%">
84
- <AreaChart
85
- data={[
86
- { day: "Mon", value: 1000 },
87
- { day: "Tue", value: 2000 },
88
- { day: "Wed", value: 3000 },
89
- { day: "Thu", value: 2500 },
90
- { day: "Fri", value: 4000 },
91
- { day: "Sat", value: 3500 },
92
- { day: "Sun", value: 4500 },
93
- ]}
94
- >
95
- <Area type="monotone" dataKey="value" stroke="hsl(var(--primary))" fill="hsl(var(--primary) / 0.2)" />
96
- </AreaChart>
97
- </ResponsiveContainer>
98
- ),
99
- },
100
- }
101
-
102
- // Example with negative change
103
- export const NegativeChange: Story = {
104
- args: {
105
- title: "Conversion Rate",
106
- description: "Percentage of visitors who make a purchase",
107
- value: "3.2%",
108
- change: 5.7,
109
- changeType: "decrease",
110
- changeLabel: "vs last month",
111
- chart: (
112
- <ResponsiveContainer width="100%" height="100%">
113
- <AreaChart
114
- data={[
115
- { week: "W1", value: 4.5 },
116
- { week: "W2", value: 4.2 },
117
- { week: "W3", value: 3.8 },
118
- { week: "W4", value: 3.5 },
119
- { week: "W5", value: 3.2 },
120
- ]}
121
- >
122
- <Area type="monotone" dataKey="value" stroke="hsl(var(--destructive))" fill="hsl(var(--destructive) / 0.2)" />
123
- </AreaChart>
124
- </ResponsiveContainer>
125
- ),
126
- },
127
- }
128
-
129
- // Loading state
130
- export const Loading: Story = {
131
- args: {
132
- title: "Revenue",
133
- description: "Total revenue this quarter",
134
- value: "$143,245",
135
- isLoading: true,
136
- chart: <div />, // This won't be shown in loading state
137
- },
138
- }
@@ -1,113 +0,0 @@
1
- import type React from "react"
2
- import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
3
- import { ChartContainer } from "@/components/ui/chart"
4
- import { cn } from "@/lib/utils"
5
- import { ArrowDownIcon, ArrowUpIcon, MoreHorizontal } from "lucide-react"
6
- import { Button } from "@/components/ui/button"
7
- import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
8
-
9
- export interface GraphCardProps {
10
- title: string
11
- description?: string
12
- chart?: React.ReactNode | any
13
- value?: string | number
14
- previousValue?: string | number
15
- change?: number
16
- changeType?: "increase" | "decrease" | "neutral"
17
- changeLabel?: string
18
- className?: string
19
- footerContent?: React.ReactNode
20
- menuItems?: Array<{ label: string; onClick: () => void }>
21
- chartConfig?: Record<string, any>
22
- isLoading?: boolean
23
- }
24
-
25
- export function GraphCard({
26
- title,
27
- description,
28
- chart,
29
- value,
30
- previousValue,
31
- change,
32
- changeType,
33
- changeLabel,
34
- className,
35
- footerContent,
36
- menuItems,
37
- chartConfig,
38
- isLoading = false,
39
- }: GraphCardProps) {
40
- const renderChangeIndicator = () => {
41
- if (changeType === "increase") {
42
- return <ArrowUpIcon className="h-4 w-4 text-emerald-500" />
43
- } else if (changeType === "decrease") {
44
- return <ArrowDownIcon className="h-4 w-4 text-red-500" />
45
- }
46
- return null
47
- }
48
-
49
- const getChangeTextColor = () => {
50
- if (changeType === "increase") return "text-emerald-500"
51
- if (changeType === "decrease") return "text-red-500"
52
- return "text-muted-foreground"
53
- }
54
-
55
- return (
56
- <Card className={cn("overflow-hidden", className)}>
57
- <CardHeader className="flex flex-row items-center justify-between pb-2">
58
- <div>
59
- <CardTitle className="text-base font-medium">{title}</CardTitle>
60
- {description && <CardDescription>{description}</CardDescription>}
61
- </div>
62
- {menuItems && menuItems.length > 0 && (
63
- <DropdownMenu>
64
- <DropdownMenuTrigger asChild>
65
- <Button variant="ghost" className="h-8 w-8 p-0">
66
- <span className="sr-only">Open menu</span>
67
- <MoreHorizontal className="h-4 w-4" />
68
- </Button>
69
- </DropdownMenuTrigger>
70
- <DropdownMenuContent align="end">
71
- {menuItems.map((item, index) => (
72
- <DropdownMenuItem key={index} onClick={item.onClick}>
73
- {item.label}
74
- </DropdownMenuItem>
75
- ))}
76
- </DropdownMenuContent>
77
- </DropdownMenu>
78
- )}
79
- </CardHeader>
80
- <CardContent className="pb-2">
81
- {value && (
82
- <div className="flex items-center justify-between">
83
- <div className="space-y-1">
84
- <p className="text-2xl font-bold">{value}</p>
85
- {(change !== undefined || previousValue) && (
86
- <div className="flex items-center text-xs">
87
- {renderChangeIndicator()}
88
- <span className={cn("ml-1", getChangeTextColor())}>
89
- {change !== undefined && `${Math.abs(change)}%`}
90
- {previousValue && !change && `from ${previousValue}`}
91
- </span>
92
- {changeLabel && <span className="ml-1 text-muted-foreground">{changeLabel}</span>}
93
- </div>
94
- )}
95
- </div>
96
- </div>
97
- )}
98
- <div className={cn("mt-4", value ? "h-[180px]" : "h-[240px]")}>
99
- {isLoading ? (
100
- <div className="flex h-full items-center justify-center">
101
- <div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent"></div>
102
- </div>
103
- ) : (chartConfig && chart) ? (
104
- <ChartContainer config={chartConfig}>{ chart }</ChartContainer>
105
- ) : (
106
- chart
107
- )}
108
- </div>
109
- </CardContent>
110
- {footerContent && <CardFooter>{footerContent}</CardFooter>}
111
- </Card>
112
- )
113
- }
@@ -1,35 +0,0 @@
1
- import { Meta, StoryObj } from "@storybook/react"
2
- import { SearchBar } from "@/components/feature/search-bar"
3
-
4
- const meta = {
5
- title: "Feature/SearchBar",
6
- component: SearchBar,
7
- parameters: {
8
- layout: "centered",
9
- },
10
- tags: ["autodocs"],
11
- } satisfies Meta<typeof SearchBar>
12
-
13
- export default meta
14
- type Story = StoryObj<typeof meta>
15
-
16
- export const Default: Story = {
17
- args: {
18
- options: [
19
- "First",
20
- "Second",
21
- "Third"
22
- ],
23
- search: [
24
- "How to use React hooks",
25
- "Tailwind CSS tutorial",
26
- "Next.js documentation",
27
- "JavaScript best practices",
28
- "TypeScript interfaces",
29
- "CSS Grid layout examples",
30
- "React performance optimization",
31
- "State management in React",
32
- ],
33
- submit: (search, option) => { console.log(`pressed ${search} ${option}`) },
34
- }
35
- }
@@ -1,141 +0,0 @@
1
- import {
2
- useEffect,
3
- useState,
4
- useRef,
5
- } from 'react'
6
-
7
- import { Search, ChevronDown } from 'lucide-react'
8
-
9
- type SearchBarProps = {
10
- search: string[],
11
- options?: string[]
12
- submit: (search:string, option?: string) => void
13
- }
14
-
15
- export const SearchBar = ({ search, options, submit }: SearchBarProps): JSX.Element => {
16
- const firstDropdownOption = (options && options.length > 0) ? options[0] : ""
17
-
18
- const [isOptionsOpen, setIsOptionsOpen] = useState(false)
19
- const [isSearchOpen, setIsSearchOpen] = useState(false)
20
- const [searchValue, setSearchValue] = useState("")
21
- const [selectedOption, setSelectedOption] = useState(firstDropdownOption)
22
-
23
- const optionsDropdownRef = useRef<HTMLDivElement>(null)
24
- const searchDropdownRef = useRef<HTMLDivElement>(null)
25
- const searchInputRef = useRef<HTMLInputElement>(null)
26
-
27
- // TODO if no dropdownOptions make it a regular search bar
28
-
29
- // Filtered search options based on input
30
- const filteredOptions = searchValue
31
- ? search.filter((option) => option?.toLowerCase().includes(searchValue.toLowerCase()))
32
- : []
33
-
34
- const onSubmit = () => {
35
- submit(searchValue, selectedOption)
36
- }
37
-
38
- // Close dropdowns when clicking outside
39
- useEffect(() => {
40
- function handleClickOutside(event: MouseEvent) {
41
- if (optionsDropdownRef.current && !optionsDropdownRef.current.contains(event.target as Node)) {
42
- setIsOptionsOpen(false)
43
- }
44
-
45
- if (
46
- searchDropdownRef.current &&
47
- !searchDropdownRef.current.contains(event.target as Node) &&
48
- event.target !== searchInputRef.current
49
-
50
- ) {
51
- setIsSearchOpen(false)
52
- }
53
- }
54
-
55
- document.addEventListener("mousedown", handleClickOutside)
56
- return () => document.removeEventListener("mousedown", handleClickOutside)
57
- }, [])
58
-
59
- // Handle search input changes
60
- const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
61
- const value = e.target.value
62
- setSearchValue(value)
63
- setIsSearchOpen(value.length > 0)
64
- }
65
-
66
- // Handle search option selection
67
- const handleSelectSearchOption = (option: string) => {
68
- setSearchValue(option)
69
- setIsSearchOpen(false)
70
- searchInputRef.current?.focus()
71
- }
72
-
73
- return (
74
- <div className="flex max-w-xl rounded-lg border border-blue-100 bg-white">
75
- <div className="relative flex-1" ref={searchDropdownRef}>
76
- <input
77
- ref={searchInputRef}
78
- type="search"
79
- placeholder="Search..."
80
- value={searchValue}
81
- onChange={handleSearchChange}
82
- onFocus={() => searchValue && setIsSearchOpen(true)}
83
- className="h-9 w-full border-0 bg-transparent px-3 py-2 outline-none"
84
- />
85
-
86
- {isSearchOpen && filteredOptions.length > 0 && (
87
- <div className="absolute left-0 top-full z-10 mt-1 w-full rounded-md border border-gray-200 bg-white py-1 shadow-lg">
88
- {filteredOptions.map((option) => (
89
- <button
90
- key={option}
91
- className="flex w-full px-4 py-2 text-left text-sm hover:bg-gray-50"
92
- onClick={() => handleSelectSearchOption(option)}
93
- >
94
- {option}
95
- </button>
96
- ))}
97
- </div>
98
- )}
99
- </div>
100
-
101
- <div className="flex items-center">
102
- <div className="h-5 w-[1px] bg-slate-200" />
103
- <div className="relative" ref={optionsDropdownRef}>
104
- <button
105
- onClick={() => setIsOptionsOpen(!isOptionsOpen)}
106
- className="flex h-9 items-center gap-1 px-3 text-sm font-normal text-gray-700 hover:bg-gray-50"
107
- >
108
- {selectedOption}
109
- <ChevronDown className="h-4 w-4 opacity-50" />
110
- </button>
111
-
112
- {isOptionsOpen && (
113
- <div className="absolute right-0 top-full z-10 mt-1 w-48 rounded-md border border-gray-200 bg-white py-1 shadow-lg">
114
- {options?.map((option) => (
115
- <button
116
- key={option}
117
- className="flex w-full px-4 py-2 text-left text-sm hover:bg-gray-50"
118
- onClick={() => {
119
- setSelectedOption(option)
120
- setIsOptionsOpen(false)
121
- }}
122
- >
123
- {option}
124
- </button>
125
- ))}
126
- </div>
127
- )}
128
- </div>
129
- <div className="h-5 w-[1px] bg-slate-200" />
130
- <button
131
- className="flex h-9 w-9 items-center justify-center text-gray-500 hover:bg-gray-50"
132
- onClick={onSubmit}
133
- >
134
- <Search className="h-2 w-2" />
135
- </button>
136
- </div>
137
- </div>
138
- )
139
- }
140
-
141
- SearchBar.displayName = "SearchBar"
@@ -1,199 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react"
2
- import { Wizard, WizardStepContent, WizardCompletion } from "./wizard"
3
- import { Button } from "@/components/ui/button"
4
- import { Input } from "@/components/ui/input"
5
- import { Label } from "@/components/ui/label"
6
- import { Card, CardContent } from "@/components/ui/card"
7
-
8
- const meta: Meta<typeof Wizard> = {
9
- title: "Feature/Wizard",
10
- component: Wizard,
11
- parameters: {
12
- layout: "centered",
13
- },
14
- tags: ["autodocs"],
15
- }
16
-
17
- export default meta
18
- type Story = StoryObj<typeof Wizard>
19
-
20
- export const Default: Story = {
21
- render: () => (
22
- <Card className="w-full max-w-2xl">
23
- <CardContent className="p-6">
24
- <Wizard
25
- steps={[
26
- { id: "step1", title: "Step 1", description: "Personal Info" },
27
- { id: "step2", title: "Step 2", description: "Account Details" },
28
- { id: "step3", title: "Step 3", description: "Confirmation" },
29
- ]}
30
- >
31
- <WizardStepContent stepId="step1">
32
- <div className="space-y-4 py-4">
33
- <h3 className="text-lg font-medium">Personal Information</h3>
34
- <p className="text-sm text-muted-foreground">Please provide your personal details.</p>
35
- <div className="grid gap-4">
36
- <div className="grid gap-2">
37
- <Label htmlFor="name">Full Name</Label>
38
- <Input id="name" placeholder="John Doe" />
39
- </div>
40
- <div className="grid gap-2">
41
- <Label htmlFor="email">Email</Label>
42
- <Input id="email" type="email" placeholder="john@example.com" />
43
- </div>
44
- </div>
45
- </div>
46
- </WizardStepContent>
47
-
48
- <WizardStepContent stepId="step2">
49
- <div className="space-y-4 py-4">
50
- <h3 className="text-lg font-medium">Account Details</h3>
51
- <p className="text-sm text-muted-foreground">Set up your account preferences.</p>
52
- <div className="grid gap-4">
53
- <div className="grid gap-2">
54
- <Label htmlFor="username">Username</Label>
55
- <Input id="username" placeholder="johndoe" />
56
- </div>
57
- <div className="grid gap-2">
58
- <Label htmlFor="password">Password</Label>
59
- <Input id="password" type="password" />
60
- </div>
61
- </div>
62
- </div>
63
- </WizardStepContent>
64
-
65
- <WizardStepContent stepId="step3">
66
- <div className="space-y-4 py-4">
67
- <h3 className="text-lg font-medium">Confirmation</h3>
68
- <p className="text-sm text-muted-foreground">Review your information before submitting.</p>
69
- <div className="rounded-lg border p-4">
70
- <div className="grid grid-cols-2 gap-4">
71
- <div className="text-sm font-medium">Name</div>
72
- <div className="text-sm">John Doe</div>
73
- <div className="text-sm font-medium">Email</div>
74
- <div className="text-sm">john@example.com</div>
75
- <div className="text-sm font-medium">Username</div>
76
- <div className="text-sm">johndoe</div>
77
- </div>
78
- </div>
79
- </div>
80
- </WizardStepContent>
81
-
82
- <WizardCompletion>
83
- <div className="flex flex-col items-center justify-center py-8 text-center">
84
- <div className="rounded-full bg-primary/10 p-3 mb-4">
85
- <svg
86
- className="h-8 w-8 text-primary"
87
- xmlns="http://www.w3.org/2000/svg"
88
- width="24"
89
- height="24"
90
- viewBox="0 0 24 24"
91
- fill="none"
92
- stroke="currentColor"
93
- strokeWidth="2"
94
- strokeLinecap="round"
95
- strokeLinejoin="round"
96
- >
97
- <path d="M20 6L9 17l-5-5" />
98
- </svg>
99
- </div>
100
- <h3 className="text-2xl font-semibold mb-2">Setup Complete!</h3>
101
- <p className="text-muted-foreground mb-6">Your account has been successfully created.</p>
102
- <Button>Go to Dashboard</Button>
103
- </div>
104
- </WizardCompletion>
105
- </Wizard>
106
- </CardContent>
107
- </Card>
108
- ),
109
- }
110
-
111
- export const WithoutStepIndicator: Story = {
112
- render: () => (
113
- <Card className="w-full max-w-2xl">
114
- <CardContent className="p-6">
115
- <Wizard
116
- steps={[
117
- { id: "step1", title: "Step 1" },
118
- { id: "step2", title: "Step 2" },
119
- { id: "step3", title: "Step 3" },
120
- ]}
121
- showStepIndicator={false}
122
- >
123
- <WizardStepContent stepId="step1">
124
- <div className="space-y-4 py-4">
125
- <h3 className="text-lg font-medium">Step 1 Content</h3>
126
- <p className="text-sm text-muted-foreground">This wizard doesn't show the step indicator.</p>
127
- </div>
128
- </WizardStepContent>
129
-
130
- <WizardStepContent stepId="step2">
131
- <div className="space-y-4 py-4">
132
- <h3 className="text-lg font-medium">Step 2 Content</h3>
133
- <p className="text-sm text-muted-foreground">Navigation is still available at the bottom.</p>
134
- </div>
135
- </WizardStepContent>
136
-
137
- <WizardStepContent stepId="step3">
138
- <div className="space-y-4 py-4">
139
- <h3 className="text-lg font-medium">Step 3 Content</h3>
140
- <p className="text-sm text-muted-foreground">This is the final step.</p>
141
- </div>
142
- </WizardStepContent>
143
-
144
- <WizardCompletion>
145
- <div className="py-4 text-center">
146
- <h3 className="text-lg font-medium">All Done!</h3>
147
- </div>
148
- </WizardCompletion>
149
- </Wizard>
150
- </CardContent>
151
- </Card>
152
- ),
153
- }
154
-
155
- export const TopNavigation: Story = {
156
- render: () => (
157
- <Card className="w-full max-w-2xl">
158
- <CardContent className="p-6">
159
- <Wizard
160
- steps={[
161
- { id: "step1", title: "Step 1" },
162
- { id: "step2", title: "Step 2" },
163
- { id: "step3", title: "Step 3" },
164
- ]}
165
- navigationPosition="top"
166
- >
167
- <WizardStepContent stepId="step1">
168
- <div className="space-y-4 py-4">
169
- <h3 className="text-lg font-medium">Step 1 Content</h3>
170
- <p className="text-sm text-muted-foreground">This wizard has navigation at the top.</p>
171
- </div>
172
- </WizardStepContent>
173
-
174
- <WizardStepContent stepId="step2">
175
- <div className="space-y-4 py-4">
176
- <h3 className="text-lg font-medium">Step 2 Content</h3>
177
- <p className="text-sm text-muted-foreground">Navigation buttons are above the content.</p>
178
- </div>
179
- </WizardStepContent>
180
-
181
- <WizardStepContent stepId="step3">
182
- <div className="space-y-4 py-4">
183
- <h3 className="text-lg font-medium">Step 3 Content</h3>
184
- <p className="text-sm text-muted-foreground">This is the final step.</p>
185
- </div>
186
- </WizardStepContent>
187
-
188
- <WizardCompletion>
189
- <div className="py-4 text-center">
190
- <h3 className="text-lg font-medium">All Done!</h3>
191
- </div>
192
- </WizardCompletion>
193
- </Wizard>
194
- </CardContent>
195
- </Card>
196
- ),
197
- }
198
-
199
-