@neoptocom/neopto-ui 1.5.3 → 1.6.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoptocom/neopto-ui",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "description": "A modern React component library built with Tailwind CSS v4 and TypeScript. Features dark mode, design tokens, and comprehensive Storybook documentation. Requires Tailwind v4+.",
6
6
  "keywords": [
@@ -167,7 +167,7 @@ export default function Autocomplete({
167
167
  >
168
168
  <fieldset
169
169
  className={[
170
- "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-13",
170
+ "w-full min-w-0 rounded-full border bg-[var(--surface)] transition-colors h-14",
171
171
  "border-[var(--border)] focus-within:border-[var(--color-brand)]",
172
172
  disabled ? "opacity-60 cursor-not-allowed" : ""
173
173
  ].join(" ")}
@@ -0,0 +1,217 @@
1
+ import * as React from "react";
2
+
3
+ export type CalendarProps = {
4
+ /** Currently selected date */
5
+ selectedDate?: Date;
6
+ /** Callback when a date is selected */
7
+ onDateSelect: (date: Date) => void;
8
+ /** Today's date (for highlighting) */
9
+ today?: Date;
10
+ /** Minimum selectable date */
11
+ minDate?: Date;
12
+ /** Maximum selectable date */
13
+ maxDate?: Date;
14
+ };
15
+
16
+ const DAYS_OF_WEEK = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
17
+ const MONTHS = [
18
+ "January",
19
+ "February",
20
+ "March",
21
+ "April",
22
+ "May",
23
+ "June",
24
+ "July",
25
+ "August",
26
+ "September",
27
+ "October",
28
+ "November",
29
+ "December",
30
+ ];
31
+
32
+ function isSameDay(date1: Date, date2: Date): boolean {
33
+ return (
34
+ date1.getDate() === date2.getDate() &&
35
+ date1.getMonth() === date2.getMonth() &&
36
+ date1.getFullYear() === date2.getFullYear()
37
+ );
38
+ }
39
+
40
+ function isSameMonth(date1: Date, date2: Date): boolean {
41
+ return (
42
+ date1.getMonth() === date2.getMonth() &&
43
+ date1.getFullYear() === date2.getFullYear()
44
+ );
45
+ }
46
+
47
+ function startOfDay(date: Date): Date {
48
+ const d = new Date(date);
49
+ d.setHours(0, 0, 0, 0);
50
+ return d;
51
+ }
52
+
53
+ export default function Calendar({
54
+ selectedDate,
55
+ onDateSelect,
56
+ today = new Date(),
57
+ minDate,
58
+ maxDate,
59
+ }: CalendarProps) {
60
+ const [currentMonth, setCurrentMonth] = React.useState(
61
+ selectedDate || new Date()
62
+ );
63
+
64
+ const todayStart = startOfDay(today);
65
+ const selectedDateStart = selectedDate ? startOfDay(selectedDate) : null;
66
+
67
+ const firstDayOfMonth = new Date(
68
+ currentMonth.getFullYear(),
69
+ currentMonth.getMonth(),
70
+ 1
71
+ );
72
+ const lastDayOfMonth = new Date(
73
+ currentMonth.getFullYear(),
74
+ currentMonth.getMonth() + 1,
75
+ 0
76
+ );
77
+ const daysInMonth = lastDayOfMonth.getDate();
78
+ const startingDayOfWeek = firstDayOfMonth.getDay();
79
+
80
+ const prevMonth = () => {
81
+ setCurrentMonth(
82
+ new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1, 1)
83
+ );
84
+ };
85
+
86
+ const nextMonth = () => {
87
+ setCurrentMonth(
88
+ new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 1)
89
+ );
90
+ };
91
+
92
+ const handleDateClick = (day: number) => {
93
+ const date = new Date(
94
+ currentMonth.getFullYear(),
95
+ currentMonth.getMonth(),
96
+ day
97
+ );
98
+ const dateStart = startOfDay(date);
99
+
100
+ if (minDate && dateStart < startOfDay(minDate)) return;
101
+ if (maxDate && dateStart > startOfDay(maxDate)) return;
102
+
103
+ onDateSelect(date);
104
+ };
105
+
106
+ const isDateDisabled = (day: number): boolean => {
107
+ const date = new Date(
108
+ currentMonth.getFullYear(),
109
+ currentMonth.getMonth(),
110
+ day
111
+ );
112
+ const dateStart = startOfDay(date);
113
+
114
+ if (minDate && dateStart < startOfDay(minDate)) return true;
115
+ if (maxDate && dateStart > startOfDay(maxDate)) return true;
116
+ return false;
117
+ };
118
+
119
+ const days = [];
120
+ // Empty cells for days before the first day of the month
121
+ for (let i = 0; i < startingDayOfWeek; i++) {
122
+ days.push(null);
123
+ }
124
+ // Days of the month
125
+ for (let day = 1; day <= daysInMonth; day++) {
126
+ days.push(day);
127
+ }
128
+
129
+ return (
130
+ <div className="w-full">
131
+ {/* Header */}
132
+ <div className="flex items-center justify-between mb-4">
133
+ <button
134
+ type="button"
135
+ onClick={prevMonth}
136
+ className="p-2 rounded-full hover:bg-[var(--muted)] transition-colors"
137
+ aria-label="Previous month"
138
+ >
139
+ <span className="material-symbols-rounded text-[var(--fg)]">
140
+ chevron_left
141
+ </span>
142
+ </button>
143
+ <h3 className="text-sm font-medium text-[var(--fg)]">
144
+ {MONTHS[currentMonth.getMonth()]} {currentMonth.getFullYear()}
145
+ </h3>
146
+ <button
147
+ type="button"
148
+ onClick={nextMonth}
149
+ className="p-2 rounded-full hover:bg-[var(--muted)] transition-colors"
150
+ aria-label="Next month"
151
+ >
152
+ <span className="material-symbols-rounded text-[var(--fg)]">
153
+ chevron_right
154
+ </span>
155
+ </button>
156
+ </div>
157
+
158
+ {/* Days of week header */}
159
+ <div className="grid grid-cols-7 gap-1 mb-2">
160
+ {DAYS_OF_WEEK.map((day) => (
161
+ <div
162
+ key={day}
163
+ className="text-xs text-center text-[var(--muted-fg)] font-medium py-1"
164
+ >
165
+ {day}
166
+ </div>
167
+ ))}
168
+ </div>
169
+
170
+ {/* Calendar grid */}
171
+ <div className="grid grid-cols-7 gap-1">
172
+ {days.map((day, idx) => {
173
+ if (day === null) {
174
+ return <div key={`empty-${idx}`} className="aspect-square" />;
175
+ }
176
+
177
+ const date = new Date(
178
+ currentMonth.getFullYear(),
179
+ currentMonth.getMonth(),
180
+ day
181
+ );
182
+ const dateStart = startOfDay(date);
183
+ const isSelected = selectedDateStart && isSameDay(dateStart, selectedDateStart);
184
+ const isToday = isSameDay(dateStart, todayStart);
185
+ const isDisabled = isDateDisabled(day);
186
+ const isCurrentMonth = isSameMonth(dateStart, currentMonth);
187
+
188
+ return (
189
+ <button
190
+ key={day}
191
+ type="button"
192
+ onClick={() => handleDateClick(day)}
193
+ disabled={isDisabled}
194
+ className={[
195
+ "aspect-square rounded-lg text-sm transition-colors",
196
+ isSelected
197
+ ? "bg-[var(--color-brand)] text-white font-medium"
198
+ : isToday
199
+ ? "bg-[var(--muted)] text-[var(--fg)] font-medium border border-[var(--color-brand)]"
200
+ : "text-[var(--fg)] hover:bg-[var(--muted)]",
201
+ isDisabled
202
+ ? "opacity-30 cursor-not-allowed"
203
+ : "cursor-pointer",
204
+ !isCurrentMonth ? "opacity-50" : "",
205
+ ]
206
+ .filter(Boolean)
207
+ .join(" ")}
208
+ >
209
+ {day}
210
+ </button>
211
+ );
212
+ })}
213
+ </div>
214
+ </div>
215
+ );
216
+ }
217
+
@@ -0,0 +1,225 @@
1
+ import * as React from "react";
2
+ import { Input } from "./Input";
3
+ import Calendar from "./Calendar";
4
+ import { Card } from "./Card";
5
+
6
+ export type DateInputProps = Omit<
7
+ React.InputHTMLAttributes<HTMLInputElement>,
8
+ "value" | "onChange" | "type"
9
+ > & {
10
+ /** Label text displayed above the input */
11
+ label?: string;
12
+ /** Current date value */
13
+ value?: Date | null;
14
+ /** Callback when date changes */
15
+ onChange: (date: Date) => void;
16
+ /** Flag to visually mark the input as errored */
17
+ error?: boolean;
18
+ /** Minimum selectable date */
19
+ minDate?: Date;
20
+ /** Maximum selectable date */
21
+ maxDate?: Date;
22
+ /** Placeholder text (default: "00/00/0000") */
23
+ placeholder?: string;
24
+ };
25
+
26
+ function formatDate(date: Date): string {
27
+ const day = String(date.getDate()).padStart(2, "0");
28
+ const month = String(date.getMonth() + 1).padStart(2, "0");
29
+ const year = date.getFullYear();
30
+ return `${day}/${month}/${year}`;
31
+ }
32
+
33
+ function parseDate(dateString: string): Date | null {
34
+ const parts = dateString.split("/");
35
+ if (parts.length !== 3) return null;
36
+
37
+ const day = parseInt(parts[0], 10);
38
+ const month = parseInt(parts[1], 10) - 1; // Month is 0-indexed
39
+ const year = parseInt(parts[2], 10);
40
+
41
+ if (isNaN(day) || isNaN(month) || isNaN(year)) return null;
42
+
43
+ const date = new Date(year, month, day);
44
+ // Check if date is valid
45
+ if (
46
+ date.getDate() !== day ||
47
+ date.getMonth() !== month ||
48
+ date.getFullYear() !== year
49
+ ) {
50
+ return null;
51
+ }
52
+
53
+ return date;
54
+ }
55
+
56
+ function isValidDate(date: Date): boolean {
57
+ return date instanceof Date && !isNaN(date.getTime());
58
+ }
59
+
60
+ function startOfDay(date: Date): Date {
61
+ const d = new Date(date);
62
+ d.setHours(0, 0, 0, 0);
63
+ return d;
64
+ }
65
+
66
+ export const DateInput = React.forwardRef<HTMLInputElement, DateInputProps>(
67
+ (
68
+ {
69
+ label,
70
+ value,
71
+ onChange,
72
+ error = false,
73
+ disabled = false,
74
+ minDate,
75
+ maxDate,
76
+ placeholder = "00/00/0000",
77
+ className = "",
78
+ ...props
79
+ },
80
+ ref
81
+ ) => {
82
+ const [inputValue, setInputValue] = React.useState(
83
+ value && isValidDate(value) ? formatDate(value) : placeholder
84
+ );
85
+ const [isFocused, setIsFocused] = React.useState(false);
86
+ const [showCalendar, setShowCalendar] = React.useState(false);
87
+ const [initialDateSet, setInitialDateSet] = React.useState(true);
88
+ const containerRef = React.useRef<HTMLDivElement>(null);
89
+
90
+ // Update input value when value prop changes
91
+ React.useEffect(() => {
92
+ if (value && isValidDate(value)) {
93
+ setInputValue(formatDate(value));
94
+ } else {
95
+ setInputValue(placeholder);
96
+ }
97
+ }, [value, placeholder]);
98
+
99
+ // Set today's date when calendar first opens
100
+ React.useEffect(() => {
101
+ if (showCalendar && initialDateSet) {
102
+ const today = new Date();
103
+ onChange(today);
104
+ setInputValue(formatDate(today));
105
+ setInitialDateSet(false);
106
+ }
107
+ }, [showCalendar, initialDateSet, onChange]);
108
+
109
+ // Handle click outside
110
+ React.useEffect(() => {
111
+ const handleClickOutside = (event: MouseEvent) => {
112
+ if (
113
+ containerRef.current &&
114
+ !containerRef.current.contains(event.target as Node)
115
+ ) {
116
+ setShowCalendar(false);
117
+ setInitialDateSet(false);
118
+ }
119
+ };
120
+
121
+ document.addEventListener("mousedown", handleClickOutside);
122
+ return () => document.removeEventListener("mousedown", handleClickOutside);
123
+ }, []);
124
+
125
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
126
+ let rawValue = e.target.value;
127
+
128
+ // Remove non-digits and format as dd/MM/yyyy
129
+ rawValue = rawValue
130
+ .replace(/\D/g, "")
131
+ .replace(/^(\d{2})/, "$1/")
132
+ .replace(/^(\d{2}\/\d{2})/, "$1/")
133
+ .slice(0, 10);
134
+
135
+ setInputValue(rawValue);
136
+
137
+ // Parse and validate when complete
138
+ if (rawValue.length === 10) {
139
+ const parsedDate = parseDate(rawValue);
140
+ if (parsedDate && isValidDate(parsedDate)) {
141
+ onChange(parsedDate);
142
+ }
143
+ }
144
+ };
145
+
146
+ const handleCalendarSelect = (date: Date) => {
147
+ const selectedDate = parseDate(inputValue);
148
+ const sameDay =
149
+ selectedDate &&
150
+ isValidDate(selectedDate) &&
151
+ selectedDate.getDate() === date.getDate() &&
152
+ selectedDate.getMonth() === date.getMonth() &&
153
+ selectedDate.getFullYear() === date.getFullYear();
154
+
155
+ if (!sameDay) {
156
+ onChange(date);
157
+ setInputValue(formatDate(date));
158
+ }
159
+
160
+ setInitialDateSet(false);
161
+ setShowCalendar(false);
162
+ };
163
+
164
+ const handleInputFocus = () => {
165
+ setIsFocused(true);
166
+ setShowCalendar(true);
167
+ };
168
+
169
+ const handleInputBlur = () => {
170
+ setIsFocused(false);
171
+ const parsed = parseDate(inputValue);
172
+ if (!parsed || !isValidDate(parsed)) {
173
+ const today = new Date();
174
+ onChange(today);
175
+ setInputValue(formatDate(today));
176
+ }
177
+ };
178
+
179
+ const isEmpty = inputValue === placeholder;
180
+ const textColorClass = isEmpty ? "text-[var(--muted-fg)]" : "";
181
+
182
+ return (
183
+ <div className={["relative w-full", className].join(" ")} ref={containerRef}>
184
+ <Input
185
+ ref={ref}
186
+ label={label}
187
+ type="text"
188
+ value={inputValue}
189
+ onChange={handleInputChange}
190
+ onFocus={handleInputFocus}
191
+ onBlur={handleInputBlur}
192
+ onClick={() => !disabled && setShowCalendar(true)}
193
+ disabled={disabled}
194
+ error={error}
195
+ icon="calendar_today"
196
+ className={textColorClass}
197
+ {...props}
198
+ />
199
+
200
+ {showCalendar && !disabled && (
201
+ <div className="absolute z-20 mt-2 w-full max-w-sm">
202
+ <Card className="p-4" showDecorations={false}>
203
+ <Calendar
204
+ selectedDate={
205
+ inputValue !== placeholder &&
206
+ parseDate(inputValue) &&
207
+ isValidDate(parseDate(inputValue)!)
208
+ ? parseDate(inputValue)!
209
+ : new Date()
210
+ }
211
+ onDateSelect={handleCalendarSelect}
212
+ today={startOfDay(new Date())}
213
+ minDate={minDate}
214
+ maxDate={maxDate}
215
+ />
216
+ </Card>
217
+ </div>
218
+ )}
219
+ </div>
220
+ );
221
+ }
222
+ );
223
+
224
+ DateInput.displayName = "DateInput";
225
+
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import Icon from "./Icon";
2
3
 
3
4
  export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> & {
4
5
  /** Input visual variant */
@@ -11,6 +12,8 @@ export type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "size
11
12
  legendProps?: React.HTMLAttributes<HTMLLegendElement>;
12
13
  /** Flag to visually mark the input as errored */
13
14
  error?: boolean;
15
+ /** Optional icon name to display on the inner right of the input */
16
+ icon?: string;
14
17
  };
15
18
 
16
19
  export const Input = React.forwardRef<HTMLInputElement, InputProps>(
@@ -23,6 +26,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
23
26
  fieldsetProps,
24
27
  legendProps,
25
28
  error = false,
29
+ icon,
26
30
  ...props
27
31
  },
28
32
  ref
@@ -30,10 +34,14 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
30
34
  const isInlineVariant = variant === "inline";
31
35
  const shouldUseInlineStyles = isInlineVariant || Boolean(label);
32
36
  const isError = error && !disabled;
37
+ const hasIcon = Boolean(icon);
33
38
 
34
39
  const inputClasses: string[] = [
35
40
  "w-full bg-transparent outline-none transition-colors",
36
- shouldUseInlineStyles ? "h-9" : "h-12 px-4 rounded-full",
41
+ shouldUseInlineStyles ? "h-9" : "h-12 rounded-full",
42
+ shouldUseInlineStyles
43
+ ? (hasIcon ? "pr-8" : "")
44
+ : (hasIcon ? "px-4 pr-10" : "px-4"),
37
45
  "font-['Poppins'] text-sm placeholder:text-[var(--muted-fg)]"
38
46
  ];
39
47
 
@@ -67,7 +75,18 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
67
75
  <input ref={ref} disabled={disabled} className={inputClassName} {...props} />
68
76
  );
69
77
 
78
+ // Standalone input (no label)
70
79
  if (!label) {
80
+ if (hasIcon) {
81
+ return (
82
+ <div className="relative">
83
+ {inputElement}
84
+ <div className="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none">
85
+ <Icon name={icon!} className="text-[var(--muted-fg)] opacity-50" />
86
+ </div>
87
+ </div>
88
+ );
89
+ }
71
90
  return inputElement;
72
91
  }
73
92
 
@@ -113,7 +132,14 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
113
132
  {label}
114
133
  </legend>
115
134
  <div className="relative flex pl-5 pr-3 pb-1 h-full">
116
- <div className="flex w-full">{inputElement}</div>
135
+ <div className="flex w-full relative">
136
+ {inputElement}
137
+ {hasIcon && (
138
+ <div className="absolute right-1 top-1/2 -translate-y-1/2 pointer-events-none">
139
+ <Icon name={icon!} className="text-[var(--muted-fg)] opacity-50" />
140
+ </div>
141
+ )}
142
+ </div>
117
143
  </div>
118
144
  </fieldset>
119
145
  );
package/src/index.ts CHANGED
@@ -23,6 +23,8 @@ export * from "./components/Chat";
23
23
  export * from "./components/MessageBubble";
24
24
  export * from "./components/Separator";
25
25
  export { Breadcrumb } from "./components/Breadcrumb";
26
+ export { DateInput } from "./components/DateInput";
27
+ export { default as Calendar } from "./components/Calendar";
26
28
 
27
29
  // Types
28
30
  export type { AppBackgroundProps } from "./components/AppBackground";
@@ -45,4 +47,6 @@ export type { CounterProps } from "./components/Counter";
45
47
  export type { AgentButtonProps } from "./components/Chat";
46
48
  export type { MessageBubbleProps } from "./components/MessageBubble";
47
49
  export type { SeparatorProps } from "./components/Separator";
48
- export type { BreadcrumbProps, BreadcrumbItem } from "./components/Breadcrumb";
50
+ export type { BreadcrumbProps, BreadcrumbItem } from "./components/Breadcrumb";
51
+ export type { DateInputProps } from "./components/DateInput";
52
+ export type { CalendarProps } from "./components/Calendar";
@@ -0,0 +1,136 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { DateInput } from "../components/DateInput";
3
+ import { useState } from "react";
4
+
5
+ const meta: Meta<typeof DateInput> = {
6
+ title: "Components/DateInput",
7
+ component: DateInput
8
+ };
9
+ export default meta;
10
+ type Story = StoryObj<typeof DateInput>;
11
+
12
+ export const Default: Story = {
13
+ render: () => {
14
+ const [date, setDate] = useState<Date | null>(null);
15
+ return (
16
+ <div className="flex flex-col gap-4 w-96">
17
+ <DateInput
18
+ label="Select date"
19
+ value={date}
20
+ onChange={(d) => setDate(d)}
21
+ />
22
+ </div>
23
+ );
24
+ }
25
+ };
26
+
27
+ export const WithInitialValue: Story = {
28
+ render: () => {
29
+ const [date, setDate] = useState<Date | null>(new Date());
30
+ return (
31
+ <div className="flex flex-col gap-4 w-96">
32
+ <DateInput
33
+ label="Date"
34
+ value={date}
35
+ onChange={(d) => setDate(d)}
36
+ />
37
+ </div>
38
+ );
39
+ }
40
+ };
41
+
42
+ export const WithoutLabel: Story = {
43
+ render: () => {
44
+ const [date, setDate] = useState<Date | null>(null);
45
+ return (
46
+ <div className="flex flex-col gap-4 w-96">
47
+ <DateInput
48
+ value={date}
49
+ onChange={(d) => setDate(d)}
50
+ />
51
+ </div>
52
+ );
53
+ }
54
+ };
55
+
56
+ export const Disabled: Story = {
57
+ render: () => {
58
+ const [date, setDate] = useState<Date | null>(new Date());
59
+ return (
60
+ <div className="flex flex-col gap-4 w-96">
61
+ <DateInput
62
+ label="Disabled date"
63
+ value={date}
64
+ onChange={(d) => setDate(d)}
65
+ disabled
66
+ />
67
+ </div>
68
+ );
69
+ }
70
+ };
71
+
72
+ export const WithError: Story = {
73
+ render: () => {
74
+ const [date, setDate] = useState<Date | null>(null);
75
+ return (
76
+ <div className="flex flex-col gap-4 w-96">
77
+ <DateInput
78
+ label="Date"
79
+ value={date}
80
+ onChange={(d) => setDate(d)}
81
+ error
82
+ />
83
+ </div>
84
+ );
85
+ }
86
+ };
87
+
88
+ export const WithMinMax: Story = {
89
+ render: () => {
90
+ const [date, setDate] = useState<Date | null>(null);
91
+ const today = new Date();
92
+ const minDate = new Date(today);
93
+ minDate.setDate(today.getDate() - 7); // 7 days ago
94
+ const maxDate = new Date(today);
95
+ maxDate.setDate(today.getDate() + 30); // 30 days from now
96
+
97
+ return (
98
+ <div className="flex flex-col gap-4 w-96">
99
+ <DateInput
100
+ label="Select date (within range)"
101
+ value={date}
102
+ onChange={(d) => setDate(d)}
103
+ minDate={minDate}
104
+ maxDate={maxDate}
105
+ />
106
+ <p className="text-xs text-[var(--muted-fg)]">
107
+ Available range: {minDate.toLocaleDateString()} to {maxDate.toLocaleDateString()}
108
+ </p>
109
+ </div>
110
+ );
111
+ }
112
+ };
113
+
114
+ export const Multiple: Story = {
115
+ render: () => {
116
+ const [startDate, setStartDate] = useState<Date | null>(null);
117
+ const [endDate, setEndDate] = useState<Date | null>(null);
118
+
119
+ return (
120
+ <div className="flex flex-col gap-4 w-96">
121
+ <DateInput
122
+ label="Start date"
123
+ value={startDate}
124
+ onChange={(d) => setStartDate(d)}
125
+ />
126
+ <DateInput
127
+ label="End date"
128
+ value={endDate}
129
+ onChange={(d) => setEndDate(d)}
130
+ minDate={startDate || undefined}
131
+ />
132
+ </div>
133
+ );
134
+ }
135
+ };
136
+
@@ -73,3 +73,14 @@ export const Error: Story = {
73
73
  </div>
74
74
  )
75
75
  };
76
+
77
+ export const WithIcon: Story = {
78
+ render: () => (
79
+ <div className="flex flex-col gap-4 w-96">
80
+ <Input variant="inline" icon="search" placeholder="Search..." />
81
+ <Input icon="email" type="email" placeholder="Email" />
82
+ <Input label="Username" icon="person" placeholder="johndoe" />
83
+ <Input label="Password" icon="lock" type="password" placeholder="Required field" error />
84
+ </div>
85
+ )
86
+ };