@axzydev/axzy_ui_system 1.2.0 → 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 -197
  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,63 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { FaList, FaTh } from "react-icons/fa";
4
+ import ITSegmentedControl from "./segmented-control";
5
+
6
+ const meta: Meta<typeof ITSegmentedControl> = {
7
+ title: "Components/Inputs/ITSegmentedControl",
8
+ component: ITSegmentedControl,
9
+ tags: ["autodocs"],
10
+ };
11
+
12
+ export default meta;
13
+ type Story = StoryObj<typeof ITSegmentedControl>;
14
+
15
+ export const Default: Story = {
16
+ render: () => {
17
+ const [val, setVal] = useState("day");
18
+ return (
19
+ <ITSegmentedControl
20
+ options={[
21
+ { value: "day", label: "Día" },
22
+ { value: "week", label: "Semana" },
23
+ { value: "month", label: "Mes" },
24
+ ]}
25
+ value={val}
26
+ onChange={setVal}
27
+ />
28
+ );
29
+ },
30
+ };
31
+
32
+ export const WithIcons: Story = {
33
+ render: () => {
34
+ const [val, setVal] = useState("list");
35
+ return (
36
+ <ITSegmentedControl
37
+ options={[
38
+ { value: "list", label: "Lista", icon: <FaList size={10} /> },
39
+ { value: "grid", label: "Grid", icon: <FaTh size={10} /> },
40
+ ]}
41
+ value={val}
42
+ onChange={setVal}
43
+ />
44
+ );
45
+ },
46
+ };
47
+
48
+ export const Small: Story = {
49
+ render: () => {
50
+ const [val, setVal] = useState("sm");
51
+ return (
52
+ <ITSegmentedControl
53
+ size="sm"
54
+ options={[
55
+ { value: "sm", label: "Chico" },
56
+ { value: "md", label: "Mediano" },
57
+ ]}
58
+ value={val}
59
+ onChange={setVal}
60
+ />
61
+ );
62
+ },
63
+ };
@@ -0,0 +1,52 @@
1
+ import clsx from "clsx";
2
+ import { ITSegmentedControlProps } from "./segmented-control.props";
3
+ import ITText from "@/components/text/text";
4
+
5
+ const sizeMap = {
6
+ sm: { button: "px-2.5 py-1.5 text-[11px]", container: "p-0.5" },
7
+ md: { button: "px-3 py-2 text-xs", container: "p-1" },
8
+ };
9
+
10
+ export default function ITSegmentedControl({
11
+ options,
12
+ value,
13
+ onChange,
14
+ size = "md",
15
+ className,
16
+ disabled = false,
17
+ }: ITSegmentedControlProps) {
18
+ const { button, container } = sizeMap[size];
19
+
20
+ return (
21
+ <div
22
+ className={clsx(
23
+ "inline-flex rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700",
24
+ container,
25
+ disabled && "opacity-50 cursor-not-allowed",
26
+ className
27
+ )}
28
+ >
29
+ {options.map((opt) => {
30
+ const isActive = opt.value === value;
31
+ return (
32
+ <button
33
+ key={opt.value}
34
+ onClick={() => !disabled && onChange(opt.value)}
35
+ disabled={disabled}
36
+ className={clsx(
37
+ button,
38
+ "rounded-lg font-semibold transition-all flex items-center gap-1.5",
39
+ isActive
40
+ ? "bg-white dark:bg-slate-700 text-slate-800 dark:text-white shadow-sm border border-slate-200 dark:border-slate-600"
41
+ : "text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300 border border-transparent",
42
+ disabled && "pointer-events-none"
43
+ )}
44
+ >
45
+ {opt.icon && <span>{opt.icon}</span>}
46
+ <ITText as="span">{opt.label}</ITText>
47
+ </button>
48
+ );
49
+ })}
50
+ </div>
51
+ );
52
+ }
@@ -0,0 +1,25 @@
1
+ import { ColorsTypes } from "@/types/colors.types";
2
+ import { SizesTypes } from "@/types/sizes.types";
3
+ export interface OptionType {
4
+ [key: string]: string;
5
+ }
6
+ export interface ITSelectProps {
7
+ name: string;
8
+ options: OptionType[];
9
+ valueField?: string;
10
+ labelField?: string;
11
+ label?: string;
12
+ placeholder?: string;
13
+ value?: string;
14
+ onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void;
15
+ onBlur?: (event: React.FocusEvent<HTMLSelectElement>) => void;
16
+ variant?: ColorsTypes;
17
+ size?: SizesTypes;
18
+ disabled?: boolean;
19
+ className?: string;
20
+ touched?: boolean;
21
+ error?: string | boolean;
22
+ required?: boolean;
23
+ autoFocus?: boolean;
24
+ readOnly?: boolean;
25
+ }
@@ -0,0 +1,86 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import ITSelect from "./select";
3
+ import { useState } from "react";
4
+
5
+ const meta: Meta<typeof ITSelect> = {
6
+ title: "Components/Form Elements/ITSelect",
7
+ component: ITSelect,
8
+ parameters: {
9
+ layout: "centered",
10
+ },
11
+ tags: ["autodocs"],
12
+ argTypes: {
13
+ disabled: { control: "boolean" },
14
+ required: { control: "boolean" },
15
+ error: { control: "text" },
16
+ },
17
+ };
18
+
19
+ export default meta;
20
+ type Story = StoryObj<typeof ITSelect>;
21
+
22
+ const options = [
23
+ { value: "option1", label: "Option 1" },
24
+ { value: "option2", label: "Option 2" },
25
+ { value: "option3", label: "Option 3" },
26
+ { value: "option4", label: "Option 4" },
27
+ ];
28
+
29
+ const SelectWrapper = (args: any) => {
30
+ const [value, setValue] = useState(args.value || "");
31
+ const [touched, setTouched] = useState(false);
32
+
33
+ return (
34
+ <div className="w-[300px]">
35
+ <ITSelect
36
+ {...args}
37
+ value={value}
38
+ onChange={(e) => setValue(e.target.value)}
39
+ onBlur={() => setTouched(true)}
40
+ touched={touched}
41
+ />
42
+ </div>
43
+ );
44
+ };
45
+
46
+ export const Default: Story = {
47
+ render: (args) => <SelectWrapper {...args} />,
48
+ args: {
49
+ name: "select",
50
+ options: options,
51
+ placeholder: "Select an option",
52
+ },
53
+ };
54
+
55
+ export const WithLabel: Story = {
56
+ render: (args) => <SelectWrapper {...args} />,
57
+ args: {
58
+ name: "select",
59
+ label: "Select Label",
60
+ options: options,
61
+ placeholder: "Select an option",
62
+ },
63
+ };
64
+
65
+ export const WithError: Story = {
66
+ render: (args) => <SelectWrapper {...args} />,
67
+ args: {
68
+ name: "select",
69
+ label: "Select with Error",
70
+ options: options,
71
+ placeholder: "Select an option",
72
+ error: "This field is required",
73
+ touched: true, // Force touched to show error immediately
74
+ },
75
+ };
76
+
77
+ export const Disabled: Story = {
78
+ render: (args) => <SelectWrapper {...args} />,
79
+ args: {
80
+ name: "select",
81
+ label: "Disabled Select",
82
+ options: options,
83
+ placeholder: "Select an option",
84
+ disabled: true,
85
+ },
86
+ };
@@ -0,0 +1,150 @@
1
+ import { theme } from "@/theme/theme";
2
+ import clsx from "clsx";
3
+ import { useState } from "react";
4
+ import { FaAngleDown } from "react-icons/fa";
5
+ import { ITSelectProps } from "./select.props";
6
+ import ITText from "@/components/text/text";
7
+
8
+ /**
9
+ * Componente de selección (select) con soporte para opciones personalizadas, validación y personalización de estilo.
10
+ * Matches styles of ITInput.
11
+ */
12
+ export default function ITSelect({
13
+ name,
14
+ options,
15
+ label,
16
+ placeholder,
17
+ valueField = "value",
18
+ labelField = "label",
19
+ value,
20
+ onChange,
21
+ onBlur,
22
+ disabled = false,
23
+ className,
24
+ touched,
25
+ required,
26
+ error,
27
+ readOnly = false,
28
+ }: ITSelectProps) {
29
+ const [isFocused, setIsFocused] = useState(false);
30
+ const [localTouched, setLocalTouched] = useState(false);
31
+
32
+ // Theme logic - reuse input theme for consistency
33
+ const inputTheme = (theme as any).input || {};
34
+
35
+ const isTouched = touched !== undefined ? touched : localTouched;
36
+ const isEmpty = value === undefined || value === null || String(value).trim() === "";
37
+
38
+ const effectiveError = error !== undefined && error !== false
39
+ ? (error === true ? "Este campo es requerido" : error)
40
+ : (required && isEmpty ? "Este campo es requerido" : undefined);
41
+
42
+ const hasError = isTouched && !!effectiveError;
43
+ const errorMessage = typeof effectiveError === "string" ? effectiveError : "Este campo es requerido";
44
+
45
+ const getStyle = () => {
46
+ const style: React.CSSProperties = {
47
+ backgroundColor: inputTheme.backgroundColor,
48
+ borderColor: inputTheme.borderColor,
49
+ borderRadius: inputTheme.borderRadius,
50
+ padding: inputTheme.padding,
51
+ fontSize: inputTheme.fontSize,
52
+ borderWidth: '1px',
53
+ borderStyle: 'solid',
54
+ transition: 'all 0.2s',
55
+ color: 'var(--input-text-color, var(--color-secondary-900))',
56
+ appearance: 'none', // Important for custom styling
57
+ };
58
+
59
+ if (disabled) {
60
+ style.backgroundColor = inputTheme.disabled?.backgroundColor || style.backgroundColor;
61
+ style.borderColor = inputTheme.disabled?.borderColor || style.borderColor;
62
+ style.opacity = 0.7;
63
+ }
64
+
65
+ if (hasError) {
66
+ style.borderColor = inputTheme.error?.borderColor || 'red';
67
+ if (isFocused) {
68
+ style.boxShadow = inputTheme.error?.ring;
69
+ }
70
+ } else if (isFocused && !readOnly) {
71
+ style.boxShadow = inputTheme.focus?.ring;
72
+ }
73
+
74
+ return style;
75
+ };
76
+
77
+ return (
78
+ <div className="w-full">
79
+ <div className={clsx("relative", {
80
+ "flex flex-col gap-1.5": label,
81
+ })}>
82
+ {label && (
83
+ <ITText
84
+ as="label"
85
+ htmlFor={name}
86
+ className={clsx(
87
+ "text-sm font-medium text-gray-700 dark:text-slate-300 pt-0",
88
+ { "text-red-500": hasError }
89
+ )}
90
+ >
91
+ <ITText as="span">{label}</ITText>
92
+ {required && <ITText as="span" className="text-red-500 ml-1">*</ITText>}
93
+ </ITText>
94
+ )}
95
+ <div className="flex flex-col w-full">
96
+ <div className="relative flex-1">
97
+ <select
98
+ name={name}
99
+ id={name}
100
+ value={value}
101
+ onChange={readOnly ? undefined : onChange}
102
+ onBlur={(e) => {
103
+ setIsFocused(false);
104
+ setLocalTouched(true);
105
+ readOnly ? undefined : onBlur?.(e);
106
+ }}
107
+ onFocus={() => setIsFocused(true)}
108
+ disabled={disabled}
109
+ className={clsx(
110
+ "w-full focus:outline-none", // Core structure only
111
+ className,
112
+ { "cursor-not-allowed": disabled }
113
+ )}
114
+ style={getStyle()}
115
+ >
116
+ <option value=""><ITText as="span">{placeholder || "Selecciona una opción"}</ITText></option>
117
+ {
118
+ readOnly ? (
119
+ <option value={value} disabled>
120
+ <ITText as="span">{options.find((option) => option[valueField] === value)?.[labelField]}</ITText>
121
+ </option>
122
+ ) : (
123
+ options.map((option) => (
124
+ <option
125
+ key={option[valueField]}
126
+ value={option[valueField]}
127
+ title={option[labelField]}
128
+ >
129
+ <ITText as="span">{option[labelField]}</ITText>
130
+ </option>
131
+ ))
132
+ )
133
+ }
134
+ </select>
135
+ <div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-gray-500">
136
+ <FaAngleDown />
137
+ </div>
138
+ </div>
139
+ {/* Validation message aligned with select */}
140
+ {hasError && (
141
+ <div className="flex-shrink-0 min-w-[140px] flex items-center pt-3">
142
+ <ITText as="p" className="text-red-500 text-xs">{errorMessage}</ITText>
143
+ </div>
144
+ )}
145
+ </div>
146
+
147
+ </div>
148
+ </div>
149
+ );
150
+ }
@@ -0,0 +1,28 @@
1
+ export interface ITNavigationSubItem {
2
+ id: string;
3
+ label: string;
4
+ action?: () => void;
5
+ isActive?: boolean;
6
+ }
7
+
8
+ export interface ITNavigationItem {
9
+ id: string;
10
+ label: string;
11
+ icon?: React.ReactNode;
12
+ action?: () => void;
13
+ isActive?: boolean;
14
+ subitems?: ITNavigationSubItem[];
15
+ badge?: string;
16
+ }
17
+
18
+ export interface ITSidebarProps {
19
+ navigationItems: ITNavigationItem[];
20
+ isCollapsed?: boolean;
21
+ onToggleCollapse?: () => void;
22
+ visibleOnMobile?: boolean;
23
+ onItemClick?: (item: ITNavigationItem) => void;
24
+ onSubItemClick?: (subitem: ITNavigationSubItem) => void;
25
+ subitemConnector?: 'dot' | '|' | 'none';
26
+ className?: string;
27
+ }
28
+
@@ -0,0 +1,117 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import ITSidebar from './sidebar';
3
+ import { FaHome, FaUsers, FaCog, FaChartBar, FaShieldAlt } from 'react-icons/fa';
4
+
5
+ const meta: Meta<typeof ITSidebar> = {
6
+ title: 'Components/Layout & Navigation/ITSidebar',
7
+ component: ITSidebar,
8
+ parameters: {
9
+ layout: 'fullscreen',
10
+ docs: {
11
+ description: {
12
+ component: 'Un sidebar moderno, verdaderamente minimalista, hermoso y personalizable con estados colapsables y submenús. Soporta theming global con hover states y glassmorphism elegantes.',
13
+ },
14
+ },
15
+ },
16
+ tags: ['autodocs'],
17
+ };
18
+
19
+ export default meta;
20
+ type Story = StoryObj<typeof ITSidebar>;
21
+
22
+ const baseNavigationItems = [
23
+ {
24
+ id: 'dashboard',
25
+ label: 'Dashboard',
26
+ icon: <FaHome />,
27
+ action: () => console.log('Dashboard clicked'),
28
+ isActive: true,
29
+ },
30
+ {
31
+ id: 'users',
32
+ label: 'Gestión de Usuarios',
33
+ icon: <FaUsers />,
34
+ badge: '3',
35
+ subitems: [
36
+ { id: 'users-list', label: 'Lista de Usuarios', action: () => console.log('Users list'), isActive: false },
37
+ { id: 'users-roles', label: 'Roles y Permisos', action: () => console.log('Roles'), isActive: false },
38
+ ],
39
+ },
40
+ {
41
+ id: 'analytics',
42
+ label: 'Analíticas',
43
+ icon: <FaChartBar />,
44
+ action: () => console.log('Analytics clicked'),
45
+ isActive: false,
46
+ },
47
+ {
48
+ id: 'security',
49
+ label: 'Seguridad',
50
+ icon: <FaShieldAlt />,
51
+ badge: '!',
52
+ action: () => console.log('Security clicked'),
53
+ isActive: false,
54
+ },
55
+ {
56
+ id: 'settings',
57
+ label: 'Configuración',
58
+ icon: <FaCog />,
59
+ subitems: [
60
+ { id: 'settings-general', label: 'General', action: () => console.log('General'), isActive: false },
61
+ { id: 'settings-theme', label: 'Apariencia', action: () => console.log('Theme'), isActive: false },
62
+ ],
63
+ },
64
+ ];
65
+
66
+ export const Default: Story = {
67
+ args: {
68
+ navigationItems: baseNavigationItems,
69
+ isCollapsed: false,
70
+ visibleOnMobile: true,
71
+ },
72
+ render: (args) => (
73
+ <div className="h-screen bg-gray-50 flex">
74
+ <ITSidebar {...args} />
75
+ <div className="flex-1 p-8 text-zinc-500 font-medium">Contenido principal simulado. Juega con el botón de colapsar para ver las transiciones suaves.</div>
76
+ </div>
77
+ ),
78
+ };
79
+
80
+ export const Collapsed: Story = {
81
+ args: {
82
+ navigationItems: baseNavigationItems,
83
+ isCollapsed: true,
84
+ visibleOnMobile: true,
85
+ },
86
+ render: (args) => (
87
+ <div className="h-screen bg-gray-50 flex">
88
+ <ITSidebar {...args} />
89
+ <div className="flex-1 p-8 text-zinc-500 font-medium">Contenido principal... ¡Pasa el cursor sobre los íconos del sidebar para ver el efecto de glassmorphism en los tooltips flotantes!</div>
90
+ </div>
91
+ ),
92
+ };
93
+
94
+ export const WithActiveSubmenu: Story = {
95
+ args: {
96
+ navigationItems: [
97
+ ...baseNavigationItems.slice(0, 1).map(i => ({...i, isActive: false})),
98
+ {
99
+ ...baseNavigationItems[1],
100
+ isActive: true,
101
+ subitems: [
102
+ { id: 'users-list', label: 'Lista de Usuarios', action: () => console.log('Users list'), isActive: true },
103
+ { id: 'users-roles', label: 'Roles y Permisos', action: () => console.log('Roles'), isActive: false },
104
+ ]
105
+ },
106
+ ...baseNavigationItems.slice(2),
107
+ ],
108
+ isCollapsed: false,
109
+ visibleOnMobile: true,
110
+ },
111
+ render: (args) => (
112
+ <div className="h-screen bg-gray-50 flex">
113
+ <ITSidebar {...args} />
114
+ <div className="flex-1 p-8 text-zinc-500 font-medium">El menú de usuarios está expandido y activo, mostrando el conector visual sutil.</div>
115
+ </div>
116
+ ),
117
+ };