@axzydev/axzy_ui_system 1.3.0 → 1.3.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/README.md +6 -2
- package/dist/index.cjs +2342 -1715
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +286 -9
- package/dist/index.d.ts +286 -9
- package/dist/index.js +2164 -1538
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.tsx +153 -153
- package/src/components/molecules/calendar/calendar.doc.mdx +2 -1
- package/src/components/molecules/calendar/calendar.tsx +102 -24
- package/src/components/molecules/masked-input/masked-input.doc.mdx +78 -0
- package/src/components/molecules/masked-input/masked-input.props.ts +58 -0
- package/src/components/molecules/masked-input/masked-input.stories.tsx +126 -0
- package/src/components/molecules/masked-input/masked-input.tsx +244 -0
- package/src/components/molecules/table/table.doc.mdx +64 -21
- package/src/components/molecules/table/table.props.ts +81 -2
- package/src/components/molecules/table/table.stories.tsx +155 -0
- package/src/components/molecules/table/table.tsx +177 -36
- package/src/components/organisms/data-table/ITDataTable.doc.mdx +37 -3
- package/src/components/organisms/data-table/ITDataTable.stories.tsx +43 -0
- package/src/components/organisms/data-table/dataTable.props.ts +64 -0
- package/src/components/organisms/data-table/dataTable.tsx +240 -82
- package/src/components/organisms/sidebar/sidebar.doc.mdx +5 -0
- package/src/components/organisms/sidebar/sidebar.props.ts +5 -0
- package/src/components/organisms/sidebar/sidebar.stories.tsx +1 -1
- package/src/components/organisms/sidebar/sidebar.tsx +27 -7
- package/src/components/organisms/topbar/topbar.doc.mdx +3 -0
- package/src/components/organisms/topbar/topbar.props.ts +16 -0
- package/src/components/organisms/topbar/topbar.tsx +133 -98
- package/src/components/templates/layout/layout.doc.mdx +2 -0
- package/src/components/templates/layout/layout.tsx +7 -1
- package/src/components/templates/page/page.doc.mdx +8 -2
- package/src/components/templates/page/page.props.ts +11 -1
- package/src/components/templates/page/page.tsx +11 -1
- package/src/dev.css +58 -1
- package/src/hooks/useElementSize.ts +46 -0
- package/src/hooks/useVirtualRows.ts +123 -0
- package/src/index.ts +6 -2
- package/src/sandbox/LandingShowcase.tsx +465 -0
- package/src/sandbox/NotFoundShowcase.tsx +70 -0
- package/src/sandbox/TopbarSearch.tsx +85 -0
- package/src/sandbox/navigation.ts +148 -0
- package/src/sandbox/useHashRoute.ts +149 -0
- package/src/showcases/DataShowcases.tsx +321 -24
- package/src/showcases/FormShowcases.tsx +178 -0
- package/src/showcases/PageShowcases.tsx +31 -0
- package/src/showcases/StructureShowcases.tsx +42 -4
- package/src/types/table.types.ts +48 -0
- package/src/utils/styles.ts +9 -4
- package/src/utils/table.utils.ts +31 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef, useState, type RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
/** Options for {@link useVirtualRows}. */
|
|
4
|
+
export interface UseVirtualRowsOptions {
|
|
5
|
+
/** Total number of rows in the dataset being windowed. */
|
|
6
|
+
count: number;
|
|
7
|
+
/** Assumed uniform row height in pixels. Values `<= 0` disable windowing. */
|
|
8
|
+
rowHeight: number;
|
|
9
|
+
/** Extra rows rendered above and below the viewport. @default 5 */
|
|
10
|
+
overscan?: number;
|
|
11
|
+
/** When `false`, no listeners are attached and the full range is returned. @default true */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* When this value changes, `scrollTop` is reset to `0` both in state and on
|
|
15
|
+
* the scroll element. Pass a page/size signature so pagination resets scroll.
|
|
16
|
+
*/
|
|
17
|
+
resetKey?: string | number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Result of {@link useVirtualRows}. */
|
|
21
|
+
export interface UseVirtualRowsResult {
|
|
22
|
+
/** Attach to the scrollable container that holds the rows. */
|
|
23
|
+
scrollRef: RefObject<HTMLDivElement | null>;
|
|
24
|
+
/** First row index (inclusive) that should be rendered. */
|
|
25
|
+
startIndex: number;
|
|
26
|
+
/** Last row index (inclusive) that should be rendered. */
|
|
27
|
+
endIndex: number;
|
|
28
|
+
/** Pixel height of the top spacer row. */
|
|
29
|
+
topSpacerHeight: number;
|
|
30
|
+
/** Pixel height of the bottom spacer row. */
|
|
31
|
+
bottomSpacerHeight: number;
|
|
32
|
+
/** Current scroll offset in pixels. */
|
|
33
|
+
scrollTop: number;
|
|
34
|
+
/** Imperatively resets scroll to the top. */
|
|
35
|
+
scrollToTop: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Zero-dependency row virtualizer for scroll containers. Tracks the scroll
|
|
40
|
+
* offset and viewport height of the element referenced by `scrollRef`, then
|
|
41
|
+
* computes the inclusive index window that must be rendered plus the spacer
|
|
42
|
+
* heights that preserve the full scroll height.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* const { scrollRef, startIndex, endIndex, topSpacerHeight, bottomSpacerHeight } =
|
|
46
|
+
* useVirtualRows({ count: rows.length, rowHeight: 44, overscan: 5, enabled: virtualized, resetKey: page });
|
|
47
|
+
* // render rows.slice(startIndex, endIndex + 1) between spacer rows of the given heights
|
|
48
|
+
*/
|
|
49
|
+
export function useVirtualRows({
|
|
50
|
+
count,
|
|
51
|
+
rowHeight,
|
|
52
|
+
overscan = 5,
|
|
53
|
+
enabled = true,
|
|
54
|
+
resetKey,
|
|
55
|
+
}: UseVirtualRowsOptions): UseVirtualRowsResult {
|
|
56
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
57
|
+
const [scrollTop, setScrollTop] = useState(0);
|
|
58
|
+
const [viewportHeight, setViewportHeight] = useState(0);
|
|
59
|
+
|
|
60
|
+
const scrollToTop = useCallback(() => {
|
|
61
|
+
setScrollTop(0);
|
|
62
|
+
const element = scrollRef.current;
|
|
63
|
+
if (element) element.scrollTop = 0;
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
useLayoutEffect(() => {
|
|
67
|
+
if (!enabled) return;
|
|
68
|
+
const element = scrollRef.current;
|
|
69
|
+
if (!element) return;
|
|
70
|
+
|
|
71
|
+
const onScroll = () => setScrollTop(element.scrollTop);
|
|
72
|
+
const measure = () => setViewportHeight(element.clientHeight);
|
|
73
|
+
|
|
74
|
+
measure();
|
|
75
|
+
setScrollTop(element.scrollTop);
|
|
76
|
+
|
|
77
|
+
element.addEventListener("scroll", onScroll, { passive: true });
|
|
78
|
+
const observer = new ResizeObserver(measure);
|
|
79
|
+
observer.observe(element);
|
|
80
|
+
|
|
81
|
+
return () => {
|
|
82
|
+
element.removeEventListener("scroll", onScroll);
|
|
83
|
+
observer.disconnect();
|
|
84
|
+
};
|
|
85
|
+
}, [enabled]);
|
|
86
|
+
|
|
87
|
+
useLayoutEffect(() => {
|
|
88
|
+
if (!enabled) return;
|
|
89
|
+
const element = scrollRef.current;
|
|
90
|
+
if (element) element.scrollTop = 0;
|
|
91
|
+
setScrollTop(0);
|
|
92
|
+
}, [resetKey, enabled]);
|
|
93
|
+
|
|
94
|
+
const valid = enabled && rowHeight > 0 && count > 0;
|
|
95
|
+
const safeScrollTop = valid ? scrollTop : 0;
|
|
96
|
+
|
|
97
|
+
const startIndex = valid
|
|
98
|
+
? Math.min(count - 1, Math.max(0, Math.floor(safeScrollTop / rowHeight) - overscan))
|
|
99
|
+
: 0;
|
|
100
|
+
const endIndex = valid
|
|
101
|
+
? Math.min(
|
|
102
|
+
count - 1,
|
|
103
|
+
Math.ceil((safeScrollTop + viewportHeight) / rowHeight) + overscan
|
|
104
|
+
)
|
|
105
|
+
: Math.max(0, count - 1);
|
|
106
|
+
|
|
107
|
+
const topSpacerHeight = valid ? startIndex * rowHeight : 0;
|
|
108
|
+
const bottomSpacerHeight = valid
|
|
109
|
+
? Math.max(0, (count - 1 - endIndex) * rowHeight)
|
|
110
|
+
: 0;
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
scrollRef,
|
|
114
|
+
startIndex,
|
|
115
|
+
endIndex,
|
|
116
|
+
topSpacerHeight,
|
|
117
|
+
bottomSpacerHeight,
|
|
118
|
+
scrollTop: safeScrollTop,
|
|
119
|
+
scrollToTop,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default useVirtualRows;
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import ITChip from "./components/atoms/chip/chip";
|
|
|
21
21
|
import ITChipInput from "./components/molecules/chip-input/chip-input";
|
|
22
22
|
import ITDropdownMenu from "./components/molecules/dropdown-menu/dropdown-menu";
|
|
23
23
|
import ITField from "./components/molecules/field/field";
|
|
24
|
+
import ITMaskedInput from "./components/molecules/masked-input/masked-input";
|
|
24
25
|
import ITMultiSelect from "./components/molecules/multi-select/multi-select";
|
|
25
26
|
import ITConfirmDialog from "./components/organisms/confirm-dialog/confirm-dialog";
|
|
26
27
|
import ITDataTable from "./components/organisms/data-table/dataTable";
|
|
@@ -75,6 +76,7 @@ import type { ITConfirmDialogProps } from "./components/organisms/confirm-dialog
|
|
|
75
76
|
import type { ITAccordionProps, ITAccordionItem } from "./components/molecules/accordion/accordion.props";
|
|
76
77
|
import type { ITDropdownMenuProps, ITDropdownMenuItem } from "./components/molecules/dropdown-menu/dropdown-menu.props";
|
|
77
78
|
import type { ITFieldProps } from "./components/molecules/field/field.props";
|
|
79
|
+
import type { ITMaskedInputProps } from "./components/molecules/masked-input/masked-input.props";
|
|
78
80
|
import type { ITMultiSelectProps, ITMultiSelectOption } from "./components/molecules/multi-select/multi-select.props";
|
|
79
81
|
import type { ITDataTableFetchParams, ITDataTableProps, ITDataTableResponse } from "./components/organisms/data-table/dataTable.props";
|
|
80
82
|
import type { ITDatePickerProps } from "./components/molecules/date-picker/date-picker.props";
|
|
@@ -113,6 +115,7 @@ import type { ITTripleFilterProps, ITTripleFilterOption } from "./components/org
|
|
|
113
115
|
import type { ITToastProps } from "./components/organisms/toast/toast.props";
|
|
114
116
|
import type { ITWysiwygProps } from "./components/molecules/wysiwyg/wysiwyg.props";
|
|
115
117
|
import type { FieldConfig, FieldConfigV2 } from "./types/field.types";
|
|
118
|
+
import type { TableDensity } from "./types/table.types";
|
|
116
119
|
|
|
117
120
|
// UTILS
|
|
118
121
|
import ITDropfile, { UploadStatus, FileTypeEnum } from "./components/organisms/dropfile/dropfile";
|
|
@@ -140,7 +143,7 @@ export {
|
|
|
140
143
|
ITAlert, ITAvatar, ITBadget, ITBreadcrumbs, ITButton,
|
|
141
144
|
ITCalendar,
|
|
142
145
|
ITCard, ITCheckbox, ITChip, ITChipInput, ITConfirmDialog, ITDataTable, ITDatePicker,
|
|
143
|
-
ITDropdownMenu, ITField, ITMultiSelect,
|
|
146
|
+
ITDropdownMenu, ITField, ITMaskedInput, ITMultiSelect,
|
|
144
147
|
ITDialog, ITDivider, ITDrawer,
|
|
145
148
|
ITDropfile, ITEmptyState, ITFlex, ITFormBuilder, ITFormHeader, ITGrid, ITImage, ITInput, ITLayout,
|
|
146
149
|
ITLoader, ITNavbar, ITPage, ITPageHeader, ITPagination, ITPopover, ITProgress,
|
|
@@ -161,7 +164,7 @@ export {
|
|
|
161
164
|
ITBreadcrumbItem, ITBreadcrumbsProps, ITButtonProps,
|
|
162
165
|
ITCalendarProps,
|
|
163
166
|
ITCardProps, ITCheckboxProps, ITChipProps, ITChipInputProps, ITConfirmDialogProps,
|
|
164
|
-
ITDropdownMenuItem, ITDropdownMenuProps, ITFieldProps, ITMultiSelectOption, ITMultiSelectProps,
|
|
167
|
+
ITDropdownMenuItem, ITDropdownMenuProps, ITFieldProps, ITMaskedInputProps, ITMultiSelectOption, ITMultiSelectProps,
|
|
165
168
|
ITDataTableFetchParams, ITDataTableProps, ITDataTableResponse, ITDatePickerProps,
|
|
166
169
|
ITDialogProps, ITDividerProps, ITDrawerProps,
|
|
167
170
|
ITEmptyStateProps, ITFlexProps,
|
|
@@ -176,6 +179,7 @@ export {
|
|
|
176
179
|
ITSegmentedControlProps, ITSidebarProps, ITSkeletonProps, ITSlideToggleProps, ITSliderProps,
|
|
177
180
|
ITStackProps, ITStatCardProps, ITStepperProps,
|
|
178
181
|
ITTableProps, ITTabsProps, ITTabItem, ITTextProps, ITTextareaProps,
|
|
182
|
+
TableDensity,
|
|
179
183
|
ITTripleFilterProps, ITTripleFilterOption, ITThemeConfig, ITThemeProviderProps, ITThemePalette,
|
|
180
184
|
ITTimePickerProps, ITToastProps,
|
|
181
185
|
ITWysiwygProps,
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
FaCheck,
|
|
4
|
+
FaCopy,
|
|
5
|
+
FaCreditCard,
|
|
6
|
+
FaGithub,
|
|
7
|
+
FaHome,
|
|
8
|
+
FaKeyboard,
|
|
9
|
+
FaLayerGroup,
|
|
10
|
+
FaNpm,
|
|
11
|
+
FaRegBell,
|
|
12
|
+
FaRocket,
|
|
13
|
+
FaSlidersH,
|
|
14
|
+
FaTable,
|
|
15
|
+
} from "react-icons/fa";
|
|
16
|
+
import ITBadget from "@/components/atoms/badget/badget";
|
|
17
|
+
import ITButton from "@/components/atoms/button/button";
|
|
18
|
+
import ITFlex from "@/components/atoms/flex/flex";
|
|
19
|
+
import ITGrid from "@/components/atoms/grid/grid";
|
|
20
|
+
import ITStack from "@/components/atoms/stack/stack";
|
|
21
|
+
import ITText from "@/components/atoms/text/text";
|
|
22
|
+
import {
|
|
23
|
+
SANDBOX_GROUPS,
|
|
24
|
+
TOTAL_ITEMS,
|
|
25
|
+
} from "./navigation";
|
|
26
|
+
|
|
27
|
+
/** Props for the sandbox landing page. */
|
|
28
|
+
export interface LandingShowcaseProps {
|
|
29
|
+
/** Open a component page by group id + item id. */
|
|
30
|
+
onOpenItem: (group: string, item: string) => void;
|
|
31
|
+
/** Navigate back to the portfolio. */
|
|
32
|
+
onOpenPortfolio: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Icon per sandbox group id. */
|
|
36
|
+
const GROUP_ICONS: Record<string, ReactNode> = {
|
|
37
|
+
general: <FaHome />,
|
|
38
|
+
struc: <FaCreditCard />,
|
|
39
|
+
forms: <FaKeyboard />,
|
|
40
|
+
data: <FaTable />,
|
|
41
|
+
nav: <FaSlidersH />,
|
|
42
|
+
feed: <FaRegBell />,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Literal per-index gradient classes for the family cards. Tailwind v4 cannot
|
|
47
|
+
* see runtime-built class names, so every gradient is written out in full.
|
|
48
|
+
*/
|
|
49
|
+
const GROUP_ACCENTS = [
|
|
50
|
+
"from-primary-500 to-info-500",
|
|
51
|
+
"from-purple-500 to-primary-500",
|
|
52
|
+
"from-primary-600 to-purple-600",
|
|
53
|
+
"from-info-500 to-purple-500",
|
|
54
|
+
"from-purple-600 to-info-600",
|
|
55
|
+
"from-primary-500 to-purple-500",
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const GITHUB_URL = "https://github.com/axzydev/axzy_ui_system";
|
|
59
|
+
const INSTALL_CMD = "pnpm add @axzydev/axzy_ui_system";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Copy text to the clipboard, falling back to a hidden textarea +
|
|
63
|
+
* `execCommand` when the async Clipboard API is unavailable (non-secure
|
|
64
|
+
* contexts, embedded webviews). Returns whether the copy succeeded.
|
|
65
|
+
*/
|
|
66
|
+
const copyText = async (text: string): Promise<boolean> => {
|
|
67
|
+
try {
|
|
68
|
+
if (navigator.clipboard?.writeText) {
|
|
69
|
+
await navigator.clipboard.writeText(text);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
/* fall through to the legacy path */
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let textarea: HTMLTextAreaElement | null = null;
|
|
77
|
+
try {
|
|
78
|
+
textarea = document.createElement("textarea");
|
|
79
|
+
textarea.value = text;
|
|
80
|
+
textarea.setAttribute("readonly", "");
|
|
81
|
+
textarea.style.position = "fixed";
|
|
82
|
+
textarea.style.opacity = "0";
|
|
83
|
+
document.body.appendChild(textarea);
|
|
84
|
+
textarea.select();
|
|
85
|
+
return document.execCommand("copy");
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
} finally {
|
|
89
|
+
textarea?.remove();
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const CopyButton = ({ text }: { text: string }) => {
|
|
94
|
+
const [copied, setCopied] = useState(false);
|
|
95
|
+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
96
|
+
|
|
97
|
+
useEffect(
|
|
98
|
+
() => () => {
|
|
99
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
100
|
+
},
|
|
101
|
+
[],
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const handleCopy = async () => {
|
|
105
|
+
const ok = await copyText(text);
|
|
106
|
+
if (!ok) return;
|
|
107
|
+
setCopied(true);
|
|
108
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
109
|
+
timeoutRef.current = setTimeout(() => setCopied(false), 2000);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<ITButton variant="text" color="gray" size="sm" onClick={handleCopy}>
|
|
114
|
+
<ITFlex align="center" gap={1.5}>
|
|
115
|
+
{copied ? (
|
|
116
|
+
<FaCheck size={10} className="text-emerald-400" />
|
|
117
|
+
) : (
|
|
118
|
+
<FaCopy size={10} />
|
|
119
|
+
)}
|
|
120
|
+
{copied ? "Copiado" : "Copiar"}
|
|
121
|
+
</ITFlex>
|
|
122
|
+
</ITButton>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const CodeBlock = ({ code, filename }: { code: string; filename?: string }) => (
|
|
127
|
+
<div className="overflow-hidden rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900">
|
|
128
|
+
{filename && (
|
|
129
|
+
<div className="px-4 py-2 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/80">
|
|
130
|
+
<ITText as="span" className="!text-xs !font-mono !text-slate-400">
|
|
131
|
+
{filename}
|
|
132
|
+
</ITText>
|
|
133
|
+
</div>
|
|
134
|
+
)}
|
|
135
|
+
<div className="relative bg-slate-950">
|
|
136
|
+
<pre className="p-4 text-xs font-mono overflow-x-auto leading-relaxed text-slate-200">
|
|
137
|
+
<code>{code}</code>
|
|
138
|
+
</pre>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const STATS = [
|
|
144
|
+
{ label: "Componentes", value: `${TOTAL_ITEMS}`, hint: "páginas documentadas" },
|
|
145
|
+
{ label: "Familias", value: `${SANDBOX_GROUPS.length}`, hint: "grupos de navegación" },
|
|
146
|
+
{ label: "TypeScript", value: "100%", hint: "tipado estricto" },
|
|
147
|
+
{ label: "UI deps", value: "0", hint: "sin runtime extra" },
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
const FEATURES = [
|
|
151
|
+
"Tailwind CSS v4 con tokens de tema en runtime",
|
|
152
|
+
"Accesibilidad: roles, foco y nombres accesibles",
|
|
153
|
+
"Dark mode automático vía ITThemeProvider",
|
|
154
|
+
"Tree-shakeable: solo importas lo que usas",
|
|
155
|
+
"Sin dependencias de UI de terceros",
|
|
156
|
+
"Documentado con ejemplos ejecutables",
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const QUICKSTART_CODE = `import { ITThemeProvider, ITButton } from "@axzydev/axzy_ui_system"
|
|
160
|
+
import "@axzydev/axzy_ui_system/dist/index.css"
|
|
161
|
+
|
|
162
|
+
export default function App() {
|
|
163
|
+
return (
|
|
164
|
+
<ITThemeProvider>
|
|
165
|
+
<ITButton variant="rounded">Empezar</ITButton>
|
|
166
|
+
</ITThemeProvider>
|
|
167
|
+
)
|
|
168
|
+
}`;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Sandbox landing page (`#ui-system`): a dashboard-style entry point with a
|
|
172
|
+
* hero, real derived stats, deep-linking family cards, quickstart, and links.
|
|
173
|
+
*/
|
|
174
|
+
export function LandingShowcase({
|
|
175
|
+
onOpenItem,
|
|
176
|
+
onOpenPortfolio,
|
|
177
|
+
}: LandingShowcaseProps) {
|
|
178
|
+
return (
|
|
179
|
+
<ITStack spacing={10} className="animate-fadeIn">
|
|
180
|
+
{/* ─── HERO ─── */}
|
|
181
|
+
<section className="relative overflow-hidden rounded-3xl bg-gradient-to-br from-primary-50 via-white to-purple-50 dark:from-primary-950/30 dark:via-slate-900 dark:to-purple-950/30 px-8 py-14 sm:px-14">
|
|
182
|
+
<div
|
|
183
|
+
aria-hidden
|
|
184
|
+
className="it-landing-float pointer-events-none absolute -top-24 -right-16 h-72 w-72 rounded-full bg-primary-400/20 blur-3xl"
|
|
185
|
+
/>
|
|
186
|
+
<div
|
|
187
|
+
aria-hidden
|
|
188
|
+
className="it-landing-float pointer-events-none absolute -bottom-24 -left-16 h-72 w-72 rounded-full bg-purple-400/20 blur-3xl"
|
|
189
|
+
style={{ animationDelay: "1.5s" }}
|
|
190
|
+
/>
|
|
191
|
+
<div
|
|
192
|
+
aria-hidden
|
|
193
|
+
className="it-landing-grid pointer-events-none absolute inset-0 text-slate-400/50 dark:text-slate-600/40"
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
<div className="relative z-10 max-w-3xl">
|
|
197
|
+
<div
|
|
198
|
+
className="it-landing-rise"
|
|
199
|
+
style={{ animationDelay: "0ms" }}
|
|
200
|
+
>
|
|
201
|
+
<ITBadget
|
|
202
|
+
label="v1.3.0 · Tailwind v4"
|
|
203
|
+
color="primary"
|
|
204
|
+
variant="outlined"
|
|
205
|
+
className="w-fit"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<ITText
|
|
210
|
+
as="h1"
|
|
211
|
+
className="it-landing-rise !mt-5 !text-4xl sm:!text-5xl !font-extrabold !tracking-tight text-slate-900 dark:text-white"
|
|
212
|
+
style={{ animationDelay: "70ms" }}
|
|
213
|
+
>
|
|
214
|
+
Componentes para{" "}
|
|
215
|
+
<span className="bg-gradient-to-r from-primary-600 via-primary-500 to-purple-500 bg-clip-text text-transparent">
|
|
216
|
+
interfaces serias
|
|
217
|
+
</span>
|
|
218
|
+
</ITText>
|
|
219
|
+
|
|
220
|
+
<ITText
|
|
221
|
+
as="p"
|
|
222
|
+
className="it-landing-rise !mt-4 !text-base !leading-relaxed max-w-xl text-slate-600 dark:text-slate-300"
|
|
223
|
+
style={{ animationDelay: "140ms" }}
|
|
224
|
+
>
|
|
225
|
+
Una librería React + TypeScript con Tailwind CSS v4: accesible,
|
|
226
|
+
temable y lista para producción. Explora {TOTAL_ITEMS} páginas en{" "}
|
|
227
|
+
{SANDBOX_GROUPS.length} familias.
|
|
228
|
+
</ITText>
|
|
229
|
+
|
|
230
|
+
<ITFlex
|
|
231
|
+
gap={3}
|
|
232
|
+
wrap="wrap"
|
|
233
|
+
align="center"
|
|
234
|
+
className="it-landing-rise !mt-7"
|
|
235
|
+
style={{ animationDelay: "210ms" }}
|
|
236
|
+
>
|
|
237
|
+
<ITButton
|
|
238
|
+
variant="filled"
|
|
239
|
+
color="primary"
|
|
240
|
+
size="lg"
|
|
241
|
+
icon={<FaRocket size={13} />}
|
|
242
|
+
onClick={() => onOpenItem("general", "getting-started")}
|
|
243
|
+
>
|
|
244
|
+
Explorar componentes
|
|
245
|
+
</ITButton>
|
|
246
|
+
<ITButton
|
|
247
|
+
variant="outlined"
|
|
248
|
+
color="secondary"
|
|
249
|
+
size="lg"
|
|
250
|
+
icon={<FaLayerGroup size={13} />}
|
|
251
|
+
onClick={onOpenPortfolio}
|
|
252
|
+
>
|
|
253
|
+
Ver portafolio
|
|
254
|
+
</ITButton>
|
|
255
|
+
</ITFlex>
|
|
256
|
+
|
|
257
|
+
<div
|
|
258
|
+
className="it-landing-rise !mt-7 inline-flex items-center gap-2 rounded-xl border border-primary-200 dark:border-primary-800/50 bg-gradient-to-r from-primary-50 to-purple-50 dark:from-primary-950/30 dark:to-purple-950/30 px-4 py-2 shadow-sm"
|
|
259
|
+
style={{ animationDelay: "280ms" }}
|
|
260
|
+
>
|
|
261
|
+
<FaNpm size={16} className="text-primary-500" />
|
|
262
|
+
<code className="text-sm font-mono text-slate-800 dark:text-slate-200">
|
|
263
|
+
{INSTALL_CMD}
|
|
264
|
+
</code>
|
|
265
|
+
<CopyButton text={INSTALL_CMD} />
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</section>
|
|
269
|
+
|
|
270
|
+
{/* ─── STAT STRIP ─── */}
|
|
271
|
+
<ITGrid container spacing={4}>
|
|
272
|
+
{STATS.map((stat) => (
|
|
273
|
+
<ITGrid item xs={6} lg={3} key={stat.label}>
|
|
274
|
+
<div className="h-full rounded-2xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-5 shadow-sm">
|
|
275
|
+
<ITText
|
|
276
|
+
as="span"
|
|
277
|
+
className="block !text-3xl !font-extrabold !tracking-tight text-slate-900 dark:text-white"
|
|
278
|
+
>
|
|
279
|
+
{stat.value}
|
|
280
|
+
</ITText>
|
|
281
|
+
<ITText
|
|
282
|
+
as="span"
|
|
283
|
+
className="mt-1 block !text-sm !font-semibold text-primary-600 dark:text-primary-400"
|
|
284
|
+
>
|
|
285
|
+
{stat.label}
|
|
286
|
+
</ITText>
|
|
287
|
+
<ITText
|
|
288
|
+
as="span"
|
|
289
|
+
className="block !text-[11px] text-slate-400 dark:text-slate-500"
|
|
290
|
+
>
|
|
291
|
+
{stat.hint}
|
|
292
|
+
</ITText>
|
|
293
|
+
</div>
|
|
294
|
+
</ITGrid>
|
|
295
|
+
))}
|
|
296
|
+
</ITGrid>
|
|
297
|
+
|
|
298
|
+
{/* ─── EXPLORE BY FAMILY ─── */}
|
|
299
|
+
<section>
|
|
300
|
+
<ITStack spacing={1} className="!mb-5">
|
|
301
|
+
<ITText
|
|
302
|
+
as="h2"
|
|
303
|
+
className="!text-xl !font-bold !tracking-tight text-slate-900 dark:text-white"
|
|
304
|
+
>
|
|
305
|
+
Explora por familia
|
|
306
|
+
</ITText>
|
|
307
|
+
<ITText as="p" className="!text-sm text-slate-500 dark:text-slate-400">
|
|
308
|
+
Cada tarjeta te lleva directo al primer componente del grupo.
|
|
309
|
+
</ITText>
|
|
310
|
+
</ITStack>
|
|
311
|
+
|
|
312
|
+
<ITGrid container spacing={5}>
|
|
313
|
+
{SANDBOX_GROUPS.map((group, index) => (
|
|
314
|
+
<ITGrid item xs={12} sm={6} lg={4} key={group.id}>
|
|
315
|
+
<button
|
|
316
|
+
type="button"
|
|
317
|
+
onClick={() => onOpenItem(group.id, group.subitems[0].id)}
|
|
318
|
+
className="group flex h-full w-full flex-col gap-4 rounded-2xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-5 text-left transition-all duration-300 hover:-translate-y-1 hover:border-primary-300 dark:hover:border-primary-700 hover:shadow-lg"
|
|
319
|
+
>
|
|
320
|
+
<div className="flex items-center gap-3">
|
|
321
|
+
<span
|
|
322
|
+
className={`flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-xl bg-gradient-to-br ${
|
|
323
|
+
GROUP_ACCENTS[index % GROUP_ACCENTS.length]
|
|
324
|
+
} text-white shadow-sm`}
|
|
325
|
+
>
|
|
326
|
+
{GROUP_ICONS[group.id]}
|
|
327
|
+
</span>
|
|
328
|
+
<span className="min-w-0 flex-1">
|
|
329
|
+
<ITText
|
|
330
|
+
as="span"
|
|
331
|
+
className="block truncate !text-sm !font-semibold text-slate-900 dark:text-white"
|
|
332
|
+
>
|
|
333
|
+
{group.label}
|
|
334
|
+
</ITText>
|
|
335
|
+
<ITText
|
|
336
|
+
as="span"
|
|
337
|
+
className="block !text-[11px] text-slate-400 dark:text-slate-500"
|
|
338
|
+
>
|
|
339
|
+
{group.subitems.length} componentes
|
|
340
|
+
</ITText>
|
|
341
|
+
</span>
|
|
342
|
+
<ITBadget
|
|
343
|
+
label={`${group.subitems.length}`}
|
|
344
|
+
color="secondary"
|
|
345
|
+
variant="outlined"
|
|
346
|
+
size="sm"
|
|
347
|
+
/>
|
|
348
|
+
</div>
|
|
349
|
+
|
|
350
|
+
<div className="flex flex-wrap gap-1.5">
|
|
351
|
+
{group.subitems.slice(0, 4).map((sub) => (
|
|
352
|
+
<span
|
|
353
|
+
key={sub.id}
|
|
354
|
+
className="rounded-md bg-slate-100 dark:bg-slate-800 px-2 py-0.5 text-[10px] font-medium text-slate-500 dark:text-slate-400"
|
|
355
|
+
>
|
|
356
|
+
{sub.label}
|
|
357
|
+
</span>
|
|
358
|
+
))}
|
|
359
|
+
{group.subitems.length > 4 && (
|
|
360
|
+
<span className="rounded-md bg-primary-50 dark:bg-primary-950/40 px-2 py-0.5 text-[10px] font-semibold text-primary-600 dark:text-primary-400">
|
|
361
|
+
+{group.subitems.length - 4} más
|
|
362
|
+
</span>
|
|
363
|
+
)}
|
|
364
|
+
</div>
|
|
365
|
+
</button>
|
|
366
|
+
</ITGrid>
|
|
367
|
+
))}
|
|
368
|
+
</ITGrid>
|
|
369
|
+
</section>
|
|
370
|
+
|
|
371
|
+
{/* ─── QUICKSTART ─── */}
|
|
372
|
+
<ITGrid container spacing={6}>
|
|
373
|
+
<ITGrid item xs={12} lg={7}>
|
|
374
|
+
<ITStack spacing={3}>
|
|
375
|
+
<ITFlex align="center" justify="between" gap={3}>
|
|
376
|
+
<ITText
|
|
377
|
+
as="h2"
|
|
378
|
+
className="!text-lg !font-bold !tracking-tight text-slate-900 dark:text-white"
|
|
379
|
+
>
|
|
380
|
+
Quickstart
|
|
381
|
+
</ITText>
|
|
382
|
+
<CopyButton text={QUICKSTART_CODE} />
|
|
383
|
+
</ITFlex>
|
|
384
|
+
<CodeBlock filename="src/App.tsx" code={QUICKSTART_CODE} />
|
|
385
|
+
</ITStack>
|
|
386
|
+
</ITGrid>
|
|
387
|
+
|
|
388
|
+
<ITGrid item xs={12} lg={5}>
|
|
389
|
+
<div className="h-full rounded-2xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-6 shadow-sm">
|
|
390
|
+
<ITText
|
|
391
|
+
as="h3"
|
|
392
|
+
className="!mb-4 !text-sm !font-bold uppercase !tracking-wider text-slate-400 dark:text-slate-500"
|
|
393
|
+
>
|
|
394
|
+
Incluido de fábrica
|
|
395
|
+
</ITText>
|
|
396
|
+
<ul className="space-y-3">
|
|
397
|
+
{FEATURES.map((feature) => (
|
|
398
|
+
<li key={feature} className="flex items-start gap-3">
|
|
399
|
+
<span className="mt-0.5 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-success-100 text-success-600 dark:bg-success-950/40 dark:text-success-400">
|
|
400
|
+
<FaCheck size={10} />
|
|
401
|
+
</span>
|
|
402
|
+
<span className="text-sm text-slate-600 dark:text-slate-300">
|
|
403
|
+
{feature}
|
|
404
|
+
</span>
|
|
405
|
+
</li>
|
|
406
|
+
))}
|
|
407
|
+
</ul>
|
|
408
|
+
</div>
|
|
409
|
+
</ITGrid>
|
|
410
|
+
</ITGrid>
|
|
411
|
+
|
|
412
|
+
{/* ─── LINKS ─── */}
|
|
413
|
+
<div className="rounded-2xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-6 shadow-sm">
|
|
414
|
+
<ITFlex wrap="wrap" gap={4} justify="between" align="center">
|
|
415
|
+
<ITStack spacing={1}>
|
|
416
|
+
<ITText
|
|
417
|
+
as="h3"
|
|
418
|
+
className="!text-base !font-bold !tracking-tight text-slate-900 dark:text-white"
|
|
419
|
+
>
|
|
420
|
+
¿Listo para construir?
|
|
421
|
+
</ITText>
|
|
422
|
+
<ITText
|
|
423
|
+
as="p"
|
|
424
|
+
className="!text-sm text-slate-500 dark:text-slate-400"
|
|
425
|
+
>
|
|
426
|
+
Revisa el portafolio, el código fuente o levanta Storybook.
|
|
427
|
+
</ITText>
|
|
428
|
+
</ITStack>
|
|
429
|
+
|
|
430
|
+
<ITFlex gap={3} wrap="wrap" align="center">
|
|
431
|
+
<ITButton
|
|
432
|
+
variant="filled"
|
|
433
|
+
color="primary"
|
|
434
|
+
icon={<FaLayerGroup size={13} />}
|
|
435
|
+
onClick={onOpenPortfolio}
|
|
436
|
+
>
|
|
437
|
+
Portafolio
|
|
438
|
+
</ITButton>
|
|
439
|
+
<ITButton
|
|
440
|
+
variant="outlined"
|
|
441
|
+
color="secondary"
|
|
442
|
+
icon={<FaGithub size={13} />}
|
|
443
|
+
onClick={() =>
|
|
444
|
+
window.open(GITHUB_URL, "_blank", "noopener,noreferrer")
|
|
445
|
+
}
|
|
446
|
+
>
|
|
447
|
+
GitHub
|
|
448
|
+
</ITButton>
|
|
449
|
+
</ITFlex>
|
|
450
|
+
</ITFlex>
|
|
451
|
+
|
|
452
|
+
<ITText
|
|
453
|
+
as="p"
|
|
454
|
+
className="!mt-5 !text-xs text-slate-400 dark:text-slate-500"
|
|
455
|
+
>
|
|
456
|
+
Ejecuta{" "}
|
|
457
|
+
<code className="rounded bg-slate-100 dark:bg-slate-800 px-1.5 py-0.5 font-mono text-slate-600 dark:text-slate-300">
|
|
458
|
+
pnpm storybook
|
|
459
|
+
</code>{" "}
|
|
460
|
+
para ver cada componente aislado.
|
|
461
|
+
</ITText>
|
|
462
|
+
</div>
|
|
463
|
+
</ITStack>
|
|
464
|
+
);
|
|
465
|
+
}
|