@axzydev/axzy_ui_system 1.2.1 → 1.2.2

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.
Files changed (202) hide show
  1. package/dist/index.css +82 -1
  2. package/dist/index.css.map +1 -1
  3. package/package.json +2 -2
  4. package/src/App.tsx +354 -0
  5. package/src/assets/logo.png +0 -0
  6. package/src/assets/react.svg +1 -0
  7. package/src/components/alert/alert.props.ts +13 -0
  8. package/src/components/alert/alert.stories.tsx +41 -0
  9. package/src/components/alert/alert.tsx +53 -0
  10. package/src/components/avatar/avatar.props.ts +14 -0
  11. package/src/components/avatar/avatar.stories.tsx +46 -0
  12. package/src/components/avatar/avatar.tsx +53 -0
  13. package/src/components/badget/badget.props.ts +12 -0
  14. package/src/components/badget/badget.stories.tsx +76 -0
  15. package/src/components/badget/badget.tsx +61 -0
  16. package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
  17. package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
  18. package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
  19. package/src/components/button/button.props.ts +18 -0
  20. package/src/components/button/button.stories.tsx +174 -0
  21. package/src/components/button/button.tsx +117 -0
  22. package/src/components/calendar/calendar.props.ts +33 -0
  23. package/src/components/calendar/calendar.stories.tsx +91 -0
  24. package/src/components/calendar/calendar.tsx +608 -0
  25. package/src/components/calendar/index.ts +3 -0
  26. package/src/components/card/card.props.ts +13 -0
  27. package/src/components/card/card.stories.tsx +58 -0
  28. package/src/components/card/card.tsx +79 -0
  29. package/src/components/checkbox/checkbox.props.ts +11 -0
  30. package/src/components/checkbox/checkbox.stories.tsx +54 -0
  31. package/src/components/checkbox/checkbox.tsx +52 -0
  32. package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
  33. package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
  34. package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
  35. package/src/components/data-table/ITDataTable.stories.tsx +213 -0
  36. package/src/components/data-table/dataTable.props.ts +69 -0
  37. package/src/components/data-table/dataTable.tsx +313 -0
  38. package/src/components/date-picker/date-picker.props.ts +30 -0
  39. package/src/components/date-picker/date-picker.stories.tsx +90 -0
  40. package/src/components/date-picker/datePicker.tsx +307 -0
  41. package/src/components/dialog/dialog.props.ts +9 -0
  42. package/src/components/dialog/dialog.stories.tsx +80 -0
  43. package/src/components/dialog/dialog.tsx +88 -0
  44. package/src/components/divider/divider.props.ts +8 -0
  45. package/src/components/divider/divider.stories.tsx +34 -0
  46. package/src/components/divider/divider.tsx +21 -0
  47. package/src/components/drawer/drawer.props.ts +14 -0
  48. package/src/components/drawer/drawer.stories.tsx +41 -0
  49. package/src/components/drawer/drawer.tsx +53 -0
  50. package/src/components/dropfile/dropfile.stories.tsx +75 -0
  51. package/src/components/dropfile/dropfile.tsx +407 -0
  52. package/src/components/empty-state/empty-state.props.ts +9 -0
  53. package/src/components/empty-state/empty-state.stories.tsx +20 -0
  54. package/src/components/empty-state/empty-state.tsx +21 -0
  55. package/src/components/flex/flex.props.ts +22 -0
  56. package/src/components/flex/flex.stories.tsx +71 -0
  57. package/src/components/flex/flex.tsx +79 -0
  58. package/src/components/form-builder/fieldRenderer.tsx +218 -0
  59. package/src/components/form-builder/formBuilder.context.tsx +70 -0
  60. package/src/components/form-builder/formBuilder.props.ts +43 -0
  61. package/src/components/form-builder/formBuilder.stories.tsx +317 -0
  62. package/src/components/form-builder/formBuilder.tsx +186 -0
  63. package/src/components/form-builder/useFormBuilder.ts +80 -0
  64. package/src/components/form-header/form-header.props.ts +5 -0
  65. package/src/components/form-header/form-header.tsx +38 -0
  66. package/src/components/grid/grid.props.ts +17 -0
  67. package/src/components/grid/grid.stories.tsx +72 -0
  68. package/src/components/grid/grid.tsx +69 -0
  69. package/src/components/image/image.props.ts +7 -0
  70. package/src/components/image/image.tsx +38 -0
  71. package/src/components/input/input.props.ts +49 -0
  72. package/src/components/input/input.stories.tsx +115 -0
  73. package/src/components/input/input.tsx +615 -0
  74. package/src/components/layout/layout.props.ts +10 -0
  75. package/src/components/layout/layout.stories.tsx +114 -0
  76. package/src/components/layout/layout.tsx +80 -0
  77. package/src/components/loader/loader.props.ts +8 -0
  78. package/src/components/loader/loader.stories.tsx +105 -0
  79. package/src/components/loader/loader.tsx +108 -0
  80. package/src/components/navbar/navbar.props.ts +37 -0
  81. package/src/components/navbar/navbar.tsx +328 -0
  82. package/src/components/page/page.props.ts +19 -0
  83. package/src/components/page/page.stories.tsx +98 -0
  84. package/src/components/page/page.tsx +90 -0
  85. package/src/components/page-header/page-header.props.ts +11 -0
  86. package/src/components/page-header/page-header.stories.tsx +61 -0
  87. package/src/components/page-header/page-header.tsx +62 -0
  88. package/src/components/pagination/pagination.props.ts +53 -0
  89. package/src/components/pagination/pagination.stories.tsx +111 -0
  90. package/src/components/pagination/pagination.tsx +241 -0
  91. package/src/components/popover/popover.props.ts +12 -0
  92. package/src/components/popover/popover.stories.tsx +25 -0
  93. package/src/components/popover/popover.tsx +45 -0
  94. package/src/components/progress/progress.props.ts +12 -0
  95. package/src/components/progress/progress.stories.tsx +40 -0
  96. package/src/components/progress/progress.tsx +52 -0
  97. package/src/components/radio/radio.props.ts +16 -0
  98. package/src/components/radio/radio.stories.tsx +50 -0
  99. package/src/components/radio/radio.tsx +58 -0
  100. package/src/components/search-select/index.ts +2 -0
  101. package/src/components/search-select/search-select.props.ts +46 -0
  102. package/src/components/search-select/search-select.stories.tsx +129 -0
  103. package/src/components/search-select/search-select.tsx +229 -0
  104. package/src/components/searchTable/components/EditableCell.tsx +149 -0
  105. package/src/components/searchTable/components/PaginationControls.tsx +86 -0
  106. package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
  107. package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
  108. package/src/components/searchTable/components/SearchInput.tsx +33 -0
  109. package/src/components/searchTable/components/SortButton.tsx +50 -0
  110. package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
  111. package/src/components/searchTable/components/TableHeader.tsx +35 -0
  112. package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
  113. package/src/components/searchTable/components/TableRow.tsx +144 -0
  114. package/src/components/searchTable/searchTable.props.ts +56 -0
  115. package/src/components/searchTable/searchTable.tsx +187 -0
  116. package/src/components/segmented-control/segmented-control.props.ts +18 -0
  117. package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
  118. package/src/components/segmented-control/segmented-control.tsx +52 -0
  119. package/src/components/select/select.props.ts +25 -0
  120. package/src/components/select/select.stories.tsx +86 -0
  121. package/src/components/select/select.tsx +150 -0
  122. package/src/components/sidebar/sidebar.props.ts +28 -0
  123. package/src/components/sidebar/sidebar.stories.tsx +117 -0
  124. package/src/components/sidebar/sidebar.tsx +313 -0
  125. package/src/components/skeleton/skeleton.props.ts +12 -0
  126. package/src/components/skeleton/skeleton.stories.tsx +30 -0
  127. package/src/components/skeleton/skeleton.tsx +45 -0
  128. package/src/components/slide/slide.props.ts +45 -0
  129. package/src/components/slide/slide.stories.tsx +121 -0
  130. package/src/components/slide/slide.tsx +109 -0
  131. package/src/components/slider/slider.props.ts +10 -0
  132. package/src/components/slider/slider.stories.tsx +30 -0
  133. package/src/components/slider/slider.tsx +49 -0
  134. package/src/components/stack/stack.props.ts +19 -0
  135. package/src/components/stack/stack.stories.tsx +79 -0
  136. package/src/components/stack/stack.tsx +79 -0
  137. package/src/components/stat-card/stat-card.props.ts +13 -0
  138. package/src/components/stat-card/stat-card.stories.tsx +41 -0
  139. package/src/components/stat-card/stat-card.tsx +44 -0
  140. package/src/components/stepper/stepper.css +26 -0
  141. package/src/components/stepper/stepper.props.ts +29 -0
  142. package/src/components/stepper/stepper.stories.tsx +155 -0
  143. package/src/components/stepper/stepper.tsx +227 -0
  144. package/src/components/table/table.props.ts +43 -0
  145. package/src/components/table/table.stories.tsx +189 -0
  146. package/src/components/table/table.tsx +376 -0
  147. package/src/components/tabs/tabs.props.ts +18 -0
  148. package/src/components/tabs/tabs.stories.tsx +32 -0
  149. package/src/components/tabs/tabs.tsx +74 -0
  150. package/src/components/text/text.props.ts +9 -0
  151. package/src/components/text/text.tsx +20 -0
  152. package/src/components/textarea/textarea.props.ts +15 -0
  153. package/src/components/textarea/textarea.stories.tsx +27 -0
  154. package/src/components/textarea/textarea.tsx +55 -0
  155. package/src/components/theme-provider/themeProvider.props.ts +28 -0
  156. package/src/components/theme-provider/themeProvider.tsx +1854 -0
  157. package/src/components/time-picker/timePicker.props.ts +16 -0
  158. package/src/components/time-picker/timePicker.stories.tsx +131 -0
  159. package/src/components/time-picker/timePicker.tsx +317 -0
  160. package/src/components/toast/toast.css +32 -0
  161. package/src/components/toast/toast.props.ts +13 -0
  162. package/src/components/toast/toast.stories.tsx +138 -0
  163. package/src/components/toast/toast.tsx +87 -0
  164. package/src/components/tooltip/tooltip.props.ts +11 -0
  165. package/src/components/tooltip/tooltip.stories.tsx +20 -0
  166. package/src/components/tooltip/tooltip.tsx +55 -0
  167. package/src/components/topbar/topbar.props.ts +21 -0
  168. package/src/components/topbar/topbar.stories.tsx +80 -0
  169. package/src/components/topbar/topbar.tsx +205 -0
  170. package/src/components/triple-filter/tripleFilter.props.ts +15 -0
  171. package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
  172. package/src/components/triple-filter/tripleFilter.tsx +50 -0
  173. package/src/hooks/useClickOutside.ts +21 -0
  174. package/src/hooks/useDebouncedSearch.ts +55 -0
  175. package/src/hooks/useEditableRow.ts +157 -0
  176. package/src/hooks/useTableState.ts +122 -0
  177. package/src/index.css +168 -0
  178. package/src/index.ts +165 -0
  179. package/src/main.tsx +9 -0
  180. package/src/showcases/DataShowcases.tsx +260 -0
  181. package/src/showcases/FeedbackShowcases.tsx +268 -0
  182. package/src/showcases/FormShowcases.tsx +1159 -0
  183. package/src/showcases/HomeShowcase.tsx +324 -0
  184. package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
  185. package/src/showcases/NavigationShowcases.tsx +193 -0
  186. package/src/showcases/PageShowcases.tsx +207 -0
  187. package/src/showcases/ShowcaseLayout.tsx +139 -0
  188. package/src/showcases/StructureShowcases.tsx +152 -0
  189. package/src/types/badget.types.ts +37 -0
  190. package/src/types/button.types.ts +16 -0
  191. package/src/types/colors.types.ts +3 -0
  192. package/src/types/field.types.ts +103 -0
  193. package/src/types/formik.types.ts +15 -0
  194. package/src/types/input.types.ts +14 -0
  195. package/src/types/loader.types.ts +9 -0
  196. package/src/types/sizes.types.ts +1 -0
  197. package/src/types/table.types.ts +15 -0
  198. package/src/types/toast.types.ts +8 -0
  199. package/src/types/yup.types.ts +11 -0
  200. package/src/utils/color.utils.ts +99 -0
  201. package/src/utils/styles.ts +120 -0
  202. package/src/utils/table.utils.ts +10 -0
@@ -0,0 +1,16 @@
1
+ export interface ITTimePickerProps {
2
+ name: string;
3
+ value?: string;
4
+ label?: string;
5
+ placeholder?: string;
6
+ onChange: (e: any) => void;
7
+ onBlur?: (e: any) => void;
8
+ required?: boolean;
9
+ touched?: boolean;
10
+ error?: string | boolean;
11
+ disabled?: boolean;
12
+ className?: string;
13
+ size?: "small" | "medium" | "large";
14
+ variant?: "primary" | "secondary" | "danger" | "success" | "warning" | "info" | "purple";
15
+ color?: "primary" | "secondary" | "danger" | "success" | "warning" | "info" | "purple" | string;
16
+ }
@@ -0,0 +1,131 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITTimePicker from "./timePicker";
3
+ import { useState } from "react";
4
+
5
+ const meta: Meta<typeof ITTimePicker> = {
6
+ title: "Components/Form Elements/ITTimePicker",
7
+ component: ITTimePicker,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ tags: ["autodocs"],
12
+ argTypes: {
13
+ color: {
14
+ control: "select",
15
+ options: ["primary", "secondary", "success", "danger", "warning", "info", "purple"],
16
+ },
17
+ size: {
18
+ control: "select",
19
+ options: ["small", "medium", "large"],
20
+ },
21
+ variant: {
22
+ control: "select",
23
+ options: ["primary", "secondary"],
24
+ },
25
+ disabled: {
26
+ control: "boolean",
27
+ },
28
+ required: {
29
+ control: "boolean",
30
+ },
31
+ error: {
32
+ control: "text",
33
+ },
34
+ },
35
+ decorators: [
36
+ (Story) => (
37
+ <div className="w-[300px]">
38
+ <Story />
39
+ </div>
40
+ ),
41
+ ],
42
+ };
43
+
44
+ export default meta;
45
+ type Story = StoryObj<typeof ITTimePicker>;
46
+
47
+ // Helper to wrap uncontrolled behavior
48
+ const TimePickerWrapper = (args: any) => {
49
+ const [value, setValue] = useState(args.value || "");
50
+ const [touched, setTouched] = useState(false);
51
+
52
+ return (
53
+ <ITTimePicker
54
+ {...args}
55
+ value={value}
56
+ touched={touched}
57
+ onChange={(e) => {
58
+ setValue(e.target.value);
59
+ if (args.onChange) args.onChange(e);
60
+ }}
61
+ onBlur={(e) => {
62
+ setTouched(true);
63
+ if (args.onBlur) args.onBlur(e);
64
+ }}
65
+ />
66
+ );
67
+ };
68
+
69
+ export const Default: Story = {
70
+ render: (args) => <TimePickerWrapper {...args} />,
71
+ args: {
72
+ name: "default_time",
73
+ label: "Select Time",
74
+ placeholder: "HH:MM",
75
+ color: "primary",
76
+ },
77
+ };
78
+
79
+ export const WithPredefinedValue: Story = {
80
+ render: (args) => <TimePickerWrapper {...args} />,
81
+ args: {
82
+ name: "predefined_time",
83
+ label: "Meeting Time",
84
+ value: "14:30",
85
+ color: "success",
86
+ },
87
+ };
88
+
89
+ export const Disabled: Story = {
90
+ render: (args) => <TimePickerWrapper {...args} />,
91
+ args: {
92
+ name: "disabled_time",
93
+ label: "Unavailable Time",
94
+ value: "09:00",
95
+ disabled: true,
96
+ },
97
+ };
98
+
99
+ export const Validation: Story = {
100
+ render: (args) => <TimePickerWrapper {...args} />,
101
+ args: {
102
+ name: "validation_time",
103
+ label: "End Time",
104
+ value: "25:99", // Invalid time string to trigger intrinsic validation
105
+ touched: true, // Force validation display
106
+ error: "Custom error message if passed explicitly",
107
+ },
108
+ };
109
+
110
+ export const Sizes: Story = {
111
+ render: (args) => (
112
+ <div className="flex flex-col gap-6">
113
+ <TimePickerWrapper {...args} size="small" label="Small TimePicker" name="sm" />
114
+ <TimePickerWrapper {...args} size="medium" label="Medium TimePicker" name="md" />
115
+ <TimePickerWrapper {...args} size="large" label="Large TimePicker" name="lg" />
116
+ </div>
117
+ ),
118
+ args: {
119
+ value: "10:15",
120
+ },
121
+ };
122
+
123
+ export const Colors: Story = {
124
+ render: (args) => (
125
+ <div className="flex flex-col gap-6">
126
+ <TimePickerWrapper {...args} color="primary" label="Primary Theme Highlight" name="c1" value="12:00" />
127
+ <TimePickerWrapper {...args} color="danger" label="Danger Theme Highlight" name="c2" value="13:15" />
128
+ <TimePickerWrapper {...args} color="purple" label="Purple Theme Highlight" name="c3" value="14:45" />
129
+ </div>
130
+ ),
131
+ };
@@ -0,0 +1,317 @@
1
+ import clsx from "clsx";
2
+ import React, { useEffect, useRef, useState } from "react";
3
+ import { FaClock } from "react-icons/fa";
4
+ import ITInput from "../input/input";
5
+ import ITButton from "../button/button";
6
+ import useClickOutside from "@/hooks/useClickOutside";
7
+ import { theme } from "@/theme/theme";
8
+ import { ITTimePickerProps } from "./timePicker.props";
9
+ import ITText from "@/components/text/text";
10
+
11
+ export default function ITTimePicker({
12
+ name,
13
+ value,
14
+ label,
15
+ placeholder = "HH:MM",
16
+ onChange,
17
+ onBlur,
18
+ required,
19
+ touched,
20
+ error,
21
+ disabled,
22
+ className,
23
+ size = "medium",
24
+ variant = "primary",
25
+ color = "primary",
26
+ }: ITTimePickerProps) {
27
+ const [isOpen, setIsOpen] = useState(false);
28
+ const [inputValue, setInputValue] = useState(value || "");
29
+ const [isValidTime, setIsValidTime] = useState(true);
30
+ const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0 });
31
+
32
+ const wrapperRef = useRef<HTMLDivElement>(null);
33
+ const dropdownRef = useRef<HTMLDivElement>(null);
34
+ const hoursRef = useRef<HTMLDivElement>(null);
35
+ const minutesRef = useRef<HTMLDivElement>(null);
36
+
37
+ useClickOutside(dropdownRef, () => {
38
+ // Only close if it's currently open to avoid setting state unnecessarily
39
+ if (isOpen) {
40
+ setIsOpen(false);
41
+ }
42
+ });
43
+
44
+ // Resolve theme color for the dropdown highlight
45
+ const isThemeColor = color in theme.colors;
46
+ const highlightColor = isThemeColor
47
+ ? theme.colors[color as keyof typeof theme.colors][50]
48
+ : "#f3f4f6"; // fallback to gray-100
49
+
50
+ const activeColor = isThemeColor
51
+ ? theme.colors[color as keyof typeof theme.colors][100]
52
+ : "#e5e7eb"; // fallback to gray-200
53
+
54
+ useEffect(() => {
55
+ setInputValue(value || "");
56
+ }, [value]);
57
+
58
+ const calculateDropdownPosition = () => {
59
+ if (wrapperRef.current) {
60
+ const inputRect = wrapperRef.current.getBoundingClientRect();
61
+ const dropdownHeight = 280; // approximate height of the time picker dropdown
62
+ const viewportHeight = window.innerHeight;
63
+
64
+ let top = inputRect.bottom + 4;
65
+ if (inputRect.bottom + dropdownHeight > viewportHeight) {
66
+ top = inputRect.top - dropdownHeight - 4;
67
+ }
68
+
69
+ setDropdownPosition({
70
+ top,
71
+ left: inputRect.left,
72
+ });
73
+ }
74
+ };
75
+
76
+ const validateTime = (timeString: string) => {
77
+ const regex = /^([01]\d|2[0-3]):([0-5]\d)$/;
78
+ return regex.test(timeString);
79
+ };
80
+
81
+ const currentHour = validateTime(inputValue) ? inputValue.split(":")[0] : null;
82
+ const currentMinute = validateTime(inputValue) ? inputValue.split(":")[1] : null;
83
+
84
+ // Auto-scroll to selected items when opened
85
+ useEffect(() => {
86
+ if (isOpen) {
87
+ setTimeout(() => {
88
+ if (hoursRef.current && currentHour) {
89
+ const selectedHourEl = hoursRef.current.querySelector(
90
+ `[data-value="${currentHour}"]`
91
+ ) as HTMLElement;
92
+ if (selectedHourEl) {
93
+ hoursRef.current.scrollTop =
94
+ selectedHourEl.offsetTop -
95
+ hoursRef.current.clientHeight / 2 +
96
+ selectedHourEl.clientHeight / 2;
97
+ }
98
+ }
99
+ if (minutesRef.current && currentMinute) {
100
+ const selectedMinuteEl = minutesRef.current.querySelector(
101
+ `[data-value="${currentMinute}"]`
102
+ ) as HTMLElement;
103
+ if (selectedMinuteEl) {
104
+ minutesRef.current.scrollTop =
105
+ selectedMinuteEl.offsetTop -
106
+ minutesRef.current.clientHeight / 2 +
107
+ selectedMinuteEl.clientHeight / 2;
108
+ }
109
+ }
110
+ }, 50);
111
+ }
112
+ }, [isOpen, currentHour, currentMinute]);
113
+
114
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
115
+ let val = e.target.value.replace(/\D/g, "");
116
+
117
+ if (val.length > 4) val = val.slice(0, 4);
118
+
119
+ if (val.length >= 3) {
120
+ val = `${val.slice(0, 2)}:${val.slice(2)}`;
121
+ }
122
+
123
+ setInputValue(val);
124
+
125
+ if (validateTime(val)) {
126
+ setIsValidTime(true);
127
+ onChange({ target: { name, value: val } });
128
+ } else {
129
+ setIsValidTime(false);
130
+ }
131
+ };
132
+
133
+ const handleBlurInput = () => {
134
+ if (!validateTime(inputValue)) {
135
+ setIsValidTime(false);
136
+ onBlur?.({ target: { name, value } });
137
+ return;
138
+ }
139
+
140
+ setIsValidTime(true);
141
+ onBlur?.({ target: { name, value: inputValue } });
142
+ };
143
+
144
+ const handleHourSelect = (h: string) => {
145
+ const min = currentMinute || "00";
146
+ const newVal = `${h}:${min}`;
147
+ setInputValue(newVal);
148
+ onChange({ target: { name, value: newVal } });
149
+ setIsValidTime(true);
150
+ };
151
+
152
+ const handleMinuteSelect = (m: string) => {
153
+ const hr = currentHour || "00";
154
+ const newVal = `${hr}:${m}`;
155
+ setInputValue(newVal);
156
+ onChange({ target: { name, value: newVal } });
157
+ setIsValidTime(true);
158
+ };
159
+
160
+ const handleConfirm = () => {
161
+ setIsOpen(false);
162
+ };
163
+
164
+ const hoursList = Array.from({ length: 24 }, (_, i) =>
165
+ i.toString().padStart(2, "0")
166
+ );
167
+ const minutesList = Array.from({ length: 60 }, (_, i) =>
168
+ i.toString().padStart(2, "0")
169
+ );
170
+
171
+ return (
172
+ <div ref={wrapperRef} className={clsx("relative w-full", className)}>
173
+ <ITInput
174
+ name={name}
175
+ label={label}
176
+ placeholder={placeholder}
177
+ type="text"
178
+ value={inputValue}
179
+ onChange={handleInputChange}
180
+ onBlur={handleBlurInput}
181
+ maxLength={5}
182
+ required={required}
183
+ disabled={disabled}
184
+ variant={variant}
185
+ size={size}
186
+ touched={touched}
187
+ error={!isValidTime ? "Hora inválida" : typeof error === 'string' ? error : undefined}
188
+ iconRight={
189
+ <FaClock
190
+ onClick={() => {
191
+ if (!disabled) {
192
+ calculateDropdownPosition();
193
+ setIsOpen(!isOpen);
194
+ }
195
+ }}
196
+ className={clsx(
197
+ "cursor-pointer transition-colors",
198
+ disabled
199
+ ? "text-slate-400 cursor-not-allowed"
200
+ : "text-slate-900 hover:text-slate-600"
201
+ )}
202
+ />
203
+ }
204
+ />
205
+
206
+ {isOpen && !disabled && (
207
+ <div
208
+ ref={dropdownRef}
209
+ className="fixed z-[9999] bg-white border border-gray-100 shadow-xl rounded-xl w-64 overflow-hidden flex flex-col animate-in fade-in zoom-in-95 duration-200 origin-top it-timepicker-dropdown"
210
+ style={{
211
+ top: `${dropdownPosition.top}px`,
212
+ left: `${dropdownPosition.left}px`,
213
+ }}
214
+ >
215
+ <div className="flex bg-gray-50 border-b border-gray-100 text-xs font-semibold text-gray-500 uppercase tracking-wider">
216
+ <ITText as="div" className="flex-1 text-center py-2 border-r border-gray-100">
217
+ Horas
218
+ </ITText>
219
+ <ITText as="div" className="flex-1 text-center py-2">Minutos</ITText>
220
+ </div>
221
+
222
+ <div className="flex h-56 relative bg-white">
223
+ {/* Hours Column */}
224
+ <div
225
+ ref={hoursRef}
226
+ className="flex-1 overflow-y-auto no-scrollbar border-r border-gray-50 scroll-smooth relative"
227
+ >
228
+ <div className="py-2">
229
+ {hoursList.map((h) => {
230
+ const isSelected = currentHour === h;
231
+ return (
232
+ <div
233
+ key={h}
234
+ data-value={h}
235
+ className={clsx(
236
+ "text-center py-2 cursor-pointer transition-all duration-200 text-sm font-medium mx-2 rounded-lg my-1",
237
+ isSelected
238
+ ? "text-slate-900 shadow-sm"
239
+ : "text-slate-600 hover:text-slate-900"
240
+ )}
241
+ style={{
242
+ backgroundColor: isSelected ? activeColor : undefined,
243
+ }}
244
+ onMouseEnter={(e) => {
245
+ if (!isSelected)
246
+ e.currentTarget.style.backgroundColor = highlightColor;
247
+ }}
248
+ onMouseLeave={(e) => {
249
+ if (!isSelected)
250
+ e.currentTarget.style.backgroundColor = "transparent";
251
+ }}
252
+ onClick={() => handleHourSelect(h)}
253
+ >
254
+ {h}
255
+ </div>
256
+ );
257
+ })}
258
+ </div>
259
+ </div>
260
+
261
+ {/* Minutes Column */}
262
+ <div
263
+ ref={minutesRef}
264
+ className="flex-1 overflow-y-auto no-scrollbar scroll-smooth relative"
265
+ >
266
+ <div className="py-2">
267
+ {minutesList.map((m) => {
268
+ const isSelected = currentMinute === m;
269
+ return (
270
+ <div
271
+ key={m}
272
+ data-value={m}
273
+ className={clsx(
274
+ "text-center py-2 cursor-pointer transition-all duration-200 text-sm font-medium mx-2 rounded-lg my-1",
275
+ isSelected
276
+ ? "text-slate-900 shadow-sm"
277
+ : "text-slate-600 hover:text-slate-900"
278
+ )}
279
+ style={{
280
+ backgroundColor: isSelected ? activeColor : undefined,
281
+ }}
282
+ onMouseEnter={(e) => {
283
+ if (!isSelected)
284
+ e.currentTarget.style.backgroundColor = highlightColor;
285
+ }}
286
+ onMouseLeave={(e) => {
287
+ if (!isSelected)
288
+ e.currentTarget.style.backgroundColor = "transparent";
289
+ }}
290
+ onClick={() => handleMinuteSelect(m)}
291
+ >
292
+ {m}
293
+ </div>
294
+ );
295
+ })}
296
+ </div>
297
+ </div>
298
+
299
+ {/* Center Selection Overlay */}
300
+ <div className="absolute top-1/2 left-0 right-0 h-10 -mt-5 bg-black/5 pointer-events-none border-y border-black/10 z-10" />
301
+ </div>
302
+
303
+ <div className="p-3 bg-gray-50 border-t border-gray-100 flex justify-end">
304
+ <ITButton
305
+ variant="solid"
306
+ color={color as any}
307
+ size="small"
308
+ onClick={handleConfirm}
309
+ >
310
+ <ITText as="span">Aceptar</ITText>
311
+ </ITButton>
312
+ </div>
313
+ </div>
314
+ )}
315
+ </div>
316
+ );
317
+ }
@@ -0,0 +1,32 @@
1
+ /* Animación de entrada */
2
+ @keyframes slideIn {
3
+ from {
4
+ transform: translateY(100%);
5
+ opacity: 0;
6
+ }
7
+ to {
8
+ transform: translateY(0);
9
+ opacity: 1;
10
+ }
11
+ }
12
+
13
+ /* Animación de salida */
14
+ @keyframes slideOut {
15
+ from {
16
+ transform: translateY(0);
17
+ opacity: 1;
18
+ }
19
+ to {
20
+ transform: translateY(100%);
21
+ opacity: 0;
22
+ }
23
+ }
24
+
25
+ /* Aplicar animaciones */
26
+ .toast-enter {
27
+ animation: slideIn 0.3s ease-out;
28
+ }
29
+
30
+ .toast-exit {
31
+ animation: slideOut 0.3s ease-out;
32
+ }
@@ -0,0 +1,13 @@
1
+ export interface ITToastProps {
2
+ message: string;
3
+ type?: "success" | "error" | "warning" | "info" | "primary" | "danger" | string;
4
+ duration?: number;
5
+ position?:
6
+ | "top-right"
7
+ | "top-center"
8
+ | "top-left"
9
+ | "bottom-right"
10
+ | "bottom-center"
11
+ | "bottom-left";
12
+ onClose?: () => void;
13
+ }
@@ -0,0 +1,138 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITToast from "./toast";
3
+ import { useState } from "react";
4
+ import ITButton from "../button/button";
5
+
6
+ const meta: Meta<typeof ITToast> = {
7
+ title: "Components/Feedback/ITToast",
8
+ component: ITToast,
9
+ parameters: {
10
+ layout: "centered",
11
+ },
12
+ tags: ["autodocs"],
13
+ argTypes: {
14
+ type: {
15
+ control: "select",
16
+ options: ["success", "error", "warning", "info", "primary", "danger"],
17
+ },
18
+ position: {
19
+ control: "select",
20
+ options: [
21
+ "top-right",
22
+ "top-center",
23
+ "top-left",
24
+ "bottom-right",
25
+ "bottom-center",
26
+ "bottom-left",
27
+ ],
28
+ },
29
+ duration: {
30
+ control: { type: "number", min: 1000, max: 10000, step: 500 },
31
+ },
32
+ message: {
33
+ control: "text",
34
+ },
35
+ },
36
+ };
37
+
38
+ export default meta;
39
+ type Story = StoryObj<typeof ITToast>;
40
+
41
+ const ToastTrigger = (args: any) => {
42
+ const [show, setShow] = useState(false);
43
+
44
+ return (
45
+ <div className="flex items-center justify-center p-12">
46
+ <ITButton
47
+ variant="solid"
48
+ color={args.type in ["success", "error", "warning", "info", "primary"] ? args.type : "primary"}
49
+ onClick={() => {
50
+ setShow(false);
51
+ // Small delay to allow react to unmount and remount a fresh toast for demo purposes
52
+ setTimeout(() => setShow(true), 10);
53
+ }}
54
+ >
55
+ Show Toast ({args.type})
56
+ </ITButton>
57
+
58
+ {show && (
59
+ <ITToast
60
+ {...args}
61
+ onClose={() => {
62
+ setShow(false);
63
+ if (args.onClose) args.onClose();
64
+ }}
65
+ />
66
+ )}
67
+ </div>
68
+ );
69
+ };
70
+
71
+ export const Default: Story = {
72
+ render: (args) => <ToastTrigger {...args} />,
73
+ args: {
74
+ message: "This is a default information message.",
75
+ type: "info",
76
+ position: "top-right",
77
+ duration: 3000,
78
+ },
79
+ };
80
+
81
+ export const Success: Story = {
82
+ render: (args) => <ToastTrigger {...args} />,
83
+ args: {
84
+ message: "Operation completed successfully!",
85
+ type: "success",
86
+ position: "top-right",
87
+ duration: 3000,
88
+ },
89
+ };
90
+
91
+ export const Error: Story = {
92
+ render: (args) => <ToastTrigger {...args} />,
93
+ args: {
94
+ message: "There was a critical error processing your request.",
95
+ type: "error", // Uses theme.colors.error or danger
96
+ position: "top-center",
97
+ duration: 5000,
98
+ },
99
+ };
100
+
101
+ export const Warning: Story = {
102
+ render: (args) => <ToastTrigger {...args} />,
103
+ args: {
104
+ message: "Please check your input values before proceeding.",
105
+ type: "warning",
106
+ position: "bottom-left",
107
+ duration: 4000,
108
+ },
109
+ };
110
+
111
+ /**
112
+ * Multiple Toasts Preview
113
+ * Note: A real implementation would manage multiple toasts via a Toast Provider Context.
114
+ * This just shows how the colors resolve visually.
115
+ */
116
+ export const AllTypesPreview = () => {
117
+ const types = ["primary", "success", "error", "warning", "info"] as const;
118
+
119
+ return (
120
+ <div className="flex flex-col gap-8 w-[400px]">
121
+ <h3 className="text-gray-500 text-sm font-semibold mb-2">Static Preview (Not positioned fixed)</h3>
122
+ <div className="flex flex-col gap-4 relative">
123
+ {types.map((type) => (
124
+ // We inline style it simply to bypass the fixed positioning just for this preview story block.
125
+ <div key={type} className="relative z-0">
126
+ <ITToast
127
+ message={`This is a ${type} notification message`}
128
+ type={type}
129
+ duration={999999}
130
+ position="top-right"
131
+ />
132
+ </div>
133
+ ))}
134
+ </div>
135
+ <p className="text-xs text-gray-400 mt-4">Note: Actual ITToasts are position: fixed and rendered at the edges of the screen according to their position prop.</p>
136
+ </div>
137
+ );
138
+ };