@axzydev/axzy_ui_system 1.2.1 → 1.2.3
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.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +82 -1
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.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/dev.css +2 -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,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
|
+
};
|