@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.
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/package.json +2 -2
- package/src/App.tsx +354 -0
- package/src/assets/logo.png +0 -0
- package/src/assets/react.svg +1 -0
- package/src/components/alert/alert.props.ts +13 -0
- package/src/components/alert/alert.stories.tsx +41 -0
- package/src/components/alert/alert.tsx +53 -0
- package/src/components/avatar/avatar.props.ts +14 -0
- package/src/components/avatar/avatar.stories.tsx +46 -0
- package/src/components/avatar/avatar.tsx +53 -0
- package/src/components/badget/badget.props.ts +12 -0
- package/src/components/badget/badget.stories.tsx +76 -0
- package/src/components/badget/badget.tsx +61 -0
- package/src/components/breadcrumbs/breadcrumbs.props.ts +13 -0
- package/src/components/breadcrumbs/breadcrumbs.stories.tsx +21 -0
- package/src/components/breadcrumbs/breadcrumbs.tsx +34 -0
- package/src/components/button/button.props.ts +18 -0
- package/src/components/button/button.stories.tsx +174 -0
- package/src/components/button/button.tsx +117 -0
- package/src/components/calendar/calendar.props.ts +33 -0
- package/src/components/calendar/calendar.stories.tsx +91 -0
- package/src/components/calendar/calendar.tsx +608 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/card/card.props.ts +13 -0
- package/src/components/card/card.stories.tsx +58 -0
- package/src/components/card/card.tsx +79 -0
- package/src/components/checkbox/checkbox.props.ts +11 -0
- package/src/components/checkbox/checkbox.stories.tsx +54 -0
- package/src/components/checkbox/checkbox.tsx +52 -0
- package/src/components/confirm-dialog/confirm-dialog.props.ts +14 -0
- package/src/components/confirm-dialog/confirm-dialog.stories.tsx +33 -0
- package/src/components/confirm-dialog/confirm-dialog.tsx +45 -0
- package/src/components/data-table/ITDataTable.stories.tsx +213 -0
- package/src/components/data-table/dataTable.props.ts +69 -0
- package/src/components/data-table/dataTable.tsx +313 -0
- package/src/components/date-picker/date-picker.props.ts +30 -0
- package/src/components/date-picker/date-picker.stories.tsx +90 -0
- package/src/components/date-picker/datePicker.tsx +307 -0
- package/src/components/dialog/dialog.props.ts +9 -0
- package/src/components/dialog/dialog.stories.tsx +80 -0
- package/src/components/dialog/dialog.tsx +88 -0
- package/src/components/divider/divider.props.ts +8 -0
- package/src/components/divider/divider.stories.tsx +34 -0
- package/src/components/divider/divider.tsx +21 -0
- package/src/components/drawer/drawer.props.ts +14 -0
- package/src/components/drawer/drawer.stories.tsx +41 -0
- package/src/components/drawer/drawer.tsx +53 -0
- package/src/components/dropfile/dropfile.stories.tsx +75 -0
- package/src/components/dropfile/dropfile.tsx +407 -0
- package/src/components/empty-state/empty-state.props.ts +9 -0
- package/src/components/empty-state/empty-state.stories.tsx +20 -0
- package/src/components/empty-state/empty-state.tsx +21 -0
- package/src/components/flex/flex.props.ts +22 -0
- package/src/components/flex/flex.stories.tsx +71 -0
- package/src/components/flex/flex.tsx +79 -0
- package/src/components/form-builder/fieldRenderer.tsx +218 -0
- package/src/components/form-builder/formBuilder.context.tsx +70 -0
- package/src/components/form-builder/formBuilder.props.ts +43 -0
- package/src/components/form-builder/formBuilder.stories.tsx +317 -0
- package/src/components/form-builder/formBuilder.tsx +186 -0
- package/src/components/form-builder/useFormBuilder.ts +80 -0
- package/src/components/form-header/form-header.props.ts +5 -0
- package/src/components/form-header/form-header.tsx +38 -0
- package/src/components/grid/grid.props.ts +17 -0
- package/src/components/grid/grid.stories.tsx +72 -0
- package/src/components/grid/grid.tsx +69 -0
- package/src/components/image/image.props.ts +7 -0
- package/src/components/image/image.tsx +38 -0
- package/src/components/input/input.props.ts +49 -0
- package/src/components/input/input.stories.tsx +115 -0
- package/src/components/input/input.tsx +615 -0
- package/src/components/layout/layout.props.ts +10 -0
- package/src/components/layout/layout.stories.tsx +114 -0
- package/src/components/layout/layout.tsx +80 -0
- package/src/components/loader/loader.props.ts +8 -0
- package/src/components/loader/loader.stories.tsx +105 -0
- package/src/components/loader/loader.tsx +108 -0
- package/src/components/navbar/navbar.props.ts +37 -0
- package/src/components/navbar/navbar.tsx +328 -0
- package/src/components/page/page.props.ts +19 -0
- package/src/components/page/page.stories.tsx +98 -0
- package/src/components/page/page.tsx +90 -0
- package/src/components/page-header/page-header.props.ts +11 -0
- package/src/components/page-header/page-header.stories.tsx +61 -0
- package/src/components/page-header/page-header.tsx +62 -0
- package/src/components/pagination/pagination.props.ts +53 -0
- package/src/components/pagination/pagination.stories.tsx +111 -0
- package/src/components/pagination/pagination.tsx +241 -0
- package/src/components/popover/popover.props.ts +12 -0
- package/src/components/popover/popover.stories.tsx +25 -0
- package/src/components/popover/popover.tsx +45 -0
- package/src/components/progress/progress.props.ts +12 -0
- package/src/components/progress/progress.stories.tsx +40 -0
- package/src/components/progress/progress.tsx +52 -0
- package/src/components/radio/radio.props.ts +16 -0
- package/src/components/radio/radio.stories.tsx +50 -0
- package/src/components/radio/radio.tsx +58 -0
- package/src/components/search-select/index.ts +2 -0
- package/src/components/search-select/search-select.props.ts +46 -0
- package/src/components/search-select/search-select.stories.tsx +129 -0
- package/src/components/search-select/search-select.tsx +229 -0
- package/src/components/searchTable/components/EditableCell.tsx +149 -0
- package/src/components/searchTable/components/PaginationControls.tsx +86 -0
- package/src/components/searchTable/components/PaginationInfo.tsx +20 -0
- package/src/components/searchTable/components/SearchAndSortBar.tsx +53 -0
- package/src/components/searchTable/components/SearchInput.tsx +33 -0
- package/src/components/searchTable/components/SortButton.tsx +50 -0
- package/src/components/searchTable/components/TableEmptyState.tsx +22 -0
- package/src/components/searchTable/components/TableHeader.tsx +35 -0
- package/src/components/searchTable/components/TableHeaderCell.tsx +43 -0
- package/src/components/searchTable/components/TableRow.tsx +144 -0
- package/src/components/searchTable/searchTable.props.ts +56 -0
- package/src/components/searchTable/searchTable.tsx +187 -0
- package/src/components/segmented-control/segmented-control.props.ts +18 -0
- package/src/components/segmented-control/segmented-control.stories.tsx +63 -0
- package/src/components/segmented-control/segmented-control.tsx +52 -0
- package/src/components/select/select.props.ts +25 -0
- package/src/components/select/select.stories.tsx +86 -0
- package/src/components/select/select.tsx +150 -0
- package/src/components/sidebar/sidebar.props.ts +28 -0
- package/src/components/sidebar/sidebar.stories.tsx +117 -0
- package/src/components/sidebar/sidebar.tsx +313 -0
- package/src/components/skeleton/skeleton.props.ts +12 -0
- package/src/components/skeleton/skeleton.stories.tsx +30 -0
- package/src/components/skeleton/skeleton.tsx +45 -0
- package/src/components/slide/slide.props.ts +45 -0
- package/src/components/slide/slide.stories.tsx +121 -0
- package/src/components/slide/slide.tsx +109 -0
- package/src/components/slider/slider.props.ts +10 -0
- package/src/components/slider/slider.stories.tsx +30 -0
- package/src/components/slider/slider.tsx +49 -0
- package/src/components/stack/stack.props.ts +19 -0
- package/src/components/stack/stack.stories.tsx +79 -0
- package/src/components/stack/stack.tsx +79 -0
- package/src/components/stat-card/stat-card.props.ts +13 -0
- package/src/components/stat-card/stat-card.stories.tsx +41 -0
- package/src/components/stat-card/stat-card.tsx +44 -0
- package/src/components/stepper/stepper.css +26 -0
- package/src/components/stepper/stepper.props.ts +29 -0
- package/src/components/stepper/stepper.stories.tsx +155 -0
- package/src/components/stepper/stepper.tsx +227 -0
- package/src/components/table/table.props.ts +43 -0
- package/src/components/table/table.stories.tsx +189 -0
- package/src/components/table/table.tsx +376 -0
- package/src/components/tabs/tabs.props.ts +18 -0
- package/src/components/tabs/tabs.stories.tsx +32 -0
- package/src/components/tabs/tabs.tsx +74 -0
- package/src/components/text/text.props.ts +9 -0
- package/src/components/text/text.tsx +20 -0
- package/src/components/textarea/textarea.props.ts +15 -0
- package/src/components/textarea/textarea.stories.tsx +27 -0
- package/src/components/textarea/textarea.tsx +55 -0
- package/src/components/theme-provider/themeProvider.props.ts +28 -0
- package/src/components/theme-provider/themeProvider.tsx +1854 -0
- package/src/components/time-picker/timePicker.props.ts +16 -0
- package/src/components/time-picker/timePicker.stories.tsx +131 -0
- package/src/components/time-picker/timePicker.tsx +317 -0
- package/src/components/toast/toast.css +32 -0
- package/src/components/toast/toast.props.ts +13 -0
- package/src/components/toast/toast.stories.tsx +138 -0
- package/src/components/toast/toast.tsx +87 -0
- package/src/components/tooltip/tooltip.props.ts +11 -0
- package/src/components/tooltip/tooltip.stories.tsx +20 -0
- package/src/components/tooltip/tooltip.tsx +55 -0
- package/src/components/topbar/topbar.props.ts +21 -0
- package/src/components/topbar/topbar.stories.tsx +80 -0
- package/src/components/topbar/topbar.tsx +205 -0
- package/src/components/triple-filter/tripleFilter.props.ts +15 -0
- package/src/components/triple-filter/tripleFilter.stories.tsx +32 -0
- package/src/components/triple-filter/tripleFilter.tsx +50 -0
- package/src/hooks/useClickOutside.ts +21 -0
- package/src/hooks/useDebouncedSearch.ts +55 -0
- package/src/hooks/useEditableRow.ts +157 -0
- package/src/hooks/useTableState.ts +122 -0
- package/src/index.css +168 -0
- package/src/index.ts +165 -0
- package/src/main.tsx +9 -0
- package/src/showcases/DataShowcases.tsx +260 -0
- package/src/showcases/FeedbackShowcases.tsx +268 -0
- package/src/showcases/FormShowcases.tsx +1159 -0
- package/src/showcases/HomeShowcase.tsx +324 -0
- package/src/showcases/LayoutPrimitivesShowcases.tsx +569 -0
- package/src/showcases/NavigationShowcases.tsx +193 -0
- package/src/showcases/PageShowcases.tsx +207 -0
- package/src/showcases/ShowcaseLayout.tsx +139 -0
- package/src/showcases/StructureShowcases.tsx +152 -0
- package/src/types/badget.types.ts +37 -0
- package/src/types/button.types.ts +16 -0
- package/src/types/colors.types.ts +3 -0
- package/src/types/field.types.ts +103 -0
- package/src/types/formik.types.ts +15 -0
- package/src/types/input.types.ts +14 -0
- package/src/types/loader.types.ts +9 -0
- package/src/types/sizes.types.ts +1 -0
- package/src/types/table.types.ts +15 -0
- package/src/types/toast.types.ts +8 -0
- package/src/types/yup.types.ts +11 -0
- package/src/utils/color.utils.ts +99 -0
- package/src/utils/styles.ts +120 -0
- 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
|
+
};
|