@axzydev/axzy_ui_system 1.7.3 → 1.8.0
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 +1845 -721
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +386 -15
- package/dist/index.d.ts +386 -15
- package/dist/index.js +1532 -418
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +4 -2
- package/src/App.tsx +6 -8
- package/src/components/atoms/button/button.tsx +7 -0
- package/src/components/atoms/flex/flex.props.ts +3 -1
- package/src/components/atoms/flex/flex.tsx +11 -3
- package/src/components/atoms/input/input.tsx +5 -0
- package/src/components/atoms/segmented-control/segmented-control.tsx +22 -5
- package/src/components/atoms/textarea/textarea.props.ts +4 -0
- package/src/components/atoms/textarea/textarea.tsx +13 -3
- package/src/components/molecules/card/card.props.ts +14 -0
- package/src/components/molecules/card/card.tsx +66 -23
- package/src/components/molecules/chip-input/chip-input.tsx +2 -0
- package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +4 -0
- package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +57 -41
- package/src/components/molecules/multi-select/multi-select.tsx +2 -0
- package/src/components/molecules/tabs/tabs.tsx +22 -8
- package/src/components/organisms/dropfile/dropfile.props.ts +8 -2
- package/src/components/organisms/dropfile/dropfile.tsx +17 -0
- package/src/components/organisms/page-header/page-header.tsx +22 -6
- package/src/components/organisms/sidebar/sidebar.tsx +8 -5
- package/src/components/organisms/topbar/topbar.props.ts +2 -0
- package/src/components/organisms/topbar/topbar.tsx +56 -34
- package/src/components/templates/layout/layout.tsx +3 -2
- package/src/components/theme-provider/themeProvider.props.ts +1 -1
- package/src/components/theme-provider/themeProvider.tsx +54 -1
- package/src/i18n/locales/en.ts +7 -0
- package/src/i18n/locales/es.ts +7 -0
- package/src/index.ts +35 -7
- package/src/json-renderer/actions/effects.test.tsx +115 -0
- package/src/json-renderer/actions/effects.ts +291 -0
- package/src/json-renderer/actions/effectsChain.test.ts +117 -0
- package/src/json-renderer/binding/binding.test.tsx +168 -0
- package/src/json-renderer/binding/resolveValue.ts +193 -0
- package/src/json-renderer/catalog/axzyCatalog.ts +5 -0
- package/src/json-renderer/estado-acciones.test.tsx +166 -0
- package/src/json-renderer/form/FormNode.tsx +92 -0
- package/src/json-renderer/form/form.test.tsx +148 -0
- package/src/json-renderer/form/formLength.test.tsx +99 -0
- package/src/json-renderer/form/rules.ts +162 -0
- package/src/json-renderer/index.ts +21 -0
- package/src/json-renderer/kernel/SchemaRenderer.tsx +83 -7
- package/src/json-renderer/kernel/capabilities.ts +30 -0
- package/src/json-renderer/kernel/renderNode.tsx +256 -3
- package/src/json-renderer/kernel/schemaRenderer.props.ts +29 -3
- package/src/json-renderer/kernel/slots.test.tsx +104 -0
- package/src/json-renderer/kernel/slots.ts +34 -0
- package/src/json-renderer/kernel/types.ts +4 -0
- package/src/json-renderer/routing/match.ts +82 -0
- package/src/json-renderer/routing/navigation.ts +71 -0
- package/src/json-renderer/routing/routes.ts +179 -0
- package/src/json-renderer/routing/routing.test.ts +147 -0
- package/src/json-renderer/seguridad.test.tsx +299 -0
- package/src/json-renderer/shared/errors.ts +1 -0
- package/src/json-renderer/stream/tolerantParse.ts +5 -0
- package/src/json-renderer/stream/useSchemaStream.test.tsx +143 -0
- package/src/json-renderer/stream/useSchemaStream.ts +227 -0
- package/src/json-renderer/validation/jsonSchema.test.ts +105 -0
- package/src/json-renderer/validation/jsonSchema.ts +317 -0
- package/src/json-renderer/validation/validate.ts +17 -0
- package/src/sandbox/axzyConfig.test.ts +4 -0
- package/src/sandbox/axzyConfig.ts +1 -1
- package/src/sandbox/i18n.ts +44 -0
- package/src/sandbox/useHashRoute.ts +2 -1
- package/src/showcases/DemoJsonDashboard.tsx +218 -103
- package/src/showcases/DemoJsonDocs.tsx +11 -11
- package/src/showcases/FeedbackShowcases.tsx +2 -1
- package/src/showcases/JsonScreenShowcase.tsx +43 -53
- package/src/showcases/StructureShowcases.tsx +17 -2
- package/src/showcases/demoApp/demoJson.test.tsx +241 -0
- package/src/showcases/demoApp/routes.test.ts +63 -0
- package/src/showcases/demoApp/schemas.test.ts +6 -17
- package/src/theme/theme-context.ts +10 -2
- package/src/theme/theme.ts +1 -1
- package/src/theme/theme.types.ts +2 -1
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"packageManager": "pnpm@10.34.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.8.0",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"sideEffects": [
|
|
10
10
|
"*.css"
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
"audit:sandbox": "node scripts/audit-sandbox.mjs",
|
|
48
48
|
"docs:llm": "pnpm ext:catalog && node scripts/build-llm-docs.mjs && node scripts/build-ai-bundle.mjs",
|
|
49
49
|
"docs:ai": "node scripts/build-ai-bundle.mjs",
|
|
50
|
-
"docs:json-renderer": "node scripts/build-json-renderer-docs.mjs"
|
|
50
|
+
"docs:json-renderer": "node scripts/build-json-renderer-docs.mjs",
|
|
51
|
+
"schema:check": "node scripts/json-renderer/check.mjs"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
54
|
"clsx": "^2.1.1",
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"@types/react-dom": "19.0.3",
|
|
73
74
|
"@types/vscode": "1.85.0",
|
|
74
75
|
"@vitejs/plugin-react-swc": "3.8.0",
|
|
76
|
+
"ajv": "8.18.0",
|
|
75
77
|
"esbuild": "^0.25.0",
|
|
76
78
|
"eslint": "9.19.0",
|
|
77
79
|
"eslint-plugin-react-hooks": "5.0.0",
|
package/src/App.tsx
CHANGED
|
@@ -70,7 +70,7 @@ import JsonRendererShowcase from "./showcases/JsonRendererShowcase";
|
|
|
70
70
|
import JsonEditorShowcase from "./showcases/JsonEditorShowcase";
|
|
71
71
|
import JsonScreenShowcase from "./showcases/JsonScreenShowcase";
|
|
72
72
|
import DemoJsonPortfolio from "./showcases/DemoJsonPortfolio";
|
|
73
|
-
import DemoJsonDashboard
|
|
73
|
+
import DemoJsonDashboard from "./showcases/DemoJsonDashboard";
|
|
74
74
|
|
|
75
75
|
// Sandbox routing / landing (dev-only, never published)
|
|
76
76
|
import {
|
|
@@ -377,18 +377,16 @@ function AppShell() {
|
|
|
377
377
|
) : route.view === "demo" ? (
|
|
378
378
|
route.group === "app" ? (
|
|
379
379
|
<DemoJsonDashboard
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
window.location.hash = `#demo/app/${pagina}`;
|
|
380
|
+
path={`/${route.item || "inicio"}`}
|
|
381
|
+
query={route.query}
|
|
382
|
+
onNavigate={(href) => {
|
|
383
|
+
window.location.hash = `#demo/app${href}`;
|
|
385
384
|
}}
|
|
386
385
|
onExit={() => {
|
|
387
386
|
window.location.hash = "#demo";
|
|
388
387
|
}}
|
|
389
|
-
filterEstado={route.query.estado ?? "Todos"}
|
|
390
388
|
onFilterChange={(estado) => {
|
|
391
|
-
const base = `#demo/app/${route.item
|
|
389
|
+
const base = `#demo/app/${route.item || "inicio"}`;
|
|
392
390
|
window.location.hash = estado === "Todos" ? base : `${base}?estado=${encodeURIComponent(estado)}`;
|
|
393
391
|
}}
|
|
394
392
|
/>
|
|
@@ -9,6 +9,7 @@ import { useState } from "react";
|
|
|
9
9
|
import { theme } from "@/theme/theme";
|
|
10
10
|
import { ITButtonProps } from "./button.props";
|
|
11
11
|
import ITText from "@/components/atoms/text/text";
|
|
12
|
+
import { useITAppearance } from "@/theme/theme-context";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Highly configurable action button supporting 8 visual variants, theme colors, 3 sizes, icons, and hover/focus states.
|
|
@@ -37,6 +38,8 @@ export default function ITButton({
|
|
|
37
38
|
}: ITButtonProps) {
|
|
38
39
|
const [isHovered, setIsHovered] = useState(false);
|
|
39
40
|
const [isFocused, setIsFocused] = useState(false);
|
|
41
|
+
// Bento: pill buttons. The radius token is only defined when the provider has no explicit `radius`.
|
|
42
|
+
const bento = useITAppearance() === "bento";
|
|
40
43
|
|
|
41
44
|
/** La variante `icon-only` siempre es un cuadrado con padding simétrico. */
|
|
42
45
|
const isIconOnly = variant === "icon-only";
|
|
@@ -72,6 +75,10 @@ export default function ITButton({
|
|
|
72
75
|
transition: themeConfig.transition || 'all 0.2s',
|
|
73
76
|
};
|
|
74
77
|
|
|
78
|
+
if (bento) {
|
|
79
|
+
style.borderRadius = `var(--it-bento-pill-radius, ${themeConfig.borderRadius})`;
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
if (variant === "rounded") {
|
|
76
83
|
style.borderRadius = "9999px";
|
|
77
84
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode, CSSProperties, ElementType } from "react";
|
|
1
|
+
import { ReactNode, CSSProperties, ElementType, UIEvent } from "react";
|
|
2
2
|
|
|
3
3
|
export type FlexDirection = "row" | "column" | "row-reverse" | "column-reverse";
|
|
4
4
|
export type FlexAlign = "start" | "end" | "center" | "stretch" | "baseline";
|
|
@@ -32,4 +32,6 @@ export interface ITFlexProps {
|
|
|
32
32
|
as?: ElementType;
|
|
33
33
|
/** Click handler */
|
|
34
34
|
onClick?: (e: React.MouseEvent) => void;
|
|
35
|
+
/** Scroll handler (useful for scrollable chat/log panels) */
|
|
36
|
+
onScroll?: (e: UIEvent<HTMLElement>) => void;
|
|
35
37
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
|
+
import { forwardRef } from "react";
|
|
2
3
|
import {
|
|
3
4
|
ITFlexProps,
|
|
4
5
|
FlexDirection,
|
|
@@ -52,7 +53,7 @@ const wrapMap: Record<FlexWrap, string> = {
|
|
|
52
53
|
* <main>Content</main>
|
|
53
54
|
* </ITFlex>
|
|
54
55
|
*/
|
|
55
|
-
|
|
56
|
+
const ITFlex = forwardRef<HTMLDivElement, ITFlexProps>(function ITFlex({
|
|
56
57
|
children,
|
|
57
58
|
direction = "row",
|
|
58
59
|
align,
|
|
@@ -66,7 +67,8 @@ export default function ITFlex({
|
|
|
66
67
|
style,
|
|
67
68
|
as: Component = "div",
|
|
68
69
|
onClick,
|
|
69
|
-
|
|
70
|
+
onScroll,
|
|
71
|
+
}, ref) {
|
|
70
72
|
const resolvedStyle: React.CSSProperties = {
|
|
71
73
|
gap: gap > 0 ? `${gap * 0.25}rem` : undefined,
|
|
72
74
|
flexGrow: typeof grow === "boolean" ? (grow ? 1 : 0) : grow,
|
|
@@ -77,6 +79,7 @@ export default function ITFlex({
|
|
|
77
79
|
|
|
78
80
|
return (
|
|
79
81
|
<Component
|
|
82
|
+
ref={ref}
|
|
80
83
|
className={clsx(
|
|
81
84
|
"flex",
|
|
82
85
|
directionMap[direction],
|
|
@@ -87,8 +90,13 @@ export default function ITFlex({
|
|
|
87
90
|
)}
|
|
88
91
|
style={resolvedStyle}
|
|
89
92
|
onClick={onClick}
|
|
93
|
+
onScroll={onScroll}
|
|
90
94
|
>
|
|
91
95
|
{children}
|
|
92
96
|
</Component>
|
|
93
97
|
);
|
|
94
|
-
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
ITFlex.displayName = "ITFlex";
|
|
101
|
+
|
|
102
|
+
export default ITFlex;
|
|
@@ -91,6 +91,11 @@ export default function ITInput({
|
|
|
91
91
|
const getStyle = () => {
|
|
92
92
|
const style = inputFieldStyle(inputTheme, size);
|
|
93
93
|
|
|
94
|
+
// Textarea is multi-line: in bento it keeps a soft radius instead of the single-line pill.
|
|
95
|
+
if (isTextArea) {
|
|
96
|
+
style.borderRadius = "var(--it-bento-field-radius, var(--radius-md))";
|
|
97
|
+
}
|
|
98
|
+
|
|
94
99
|
if (disabled) {
|
|
95
100
|
style.backgroundColor = inputTheme.disabled?.backgroundColor || style.backgroundColor;
|
|
96
101
|
style.borderColor = inputTheme.disabled?.borderColor || style.borderColor;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
import { ITSegmentedControlProps } from "./segmented-control.props";
|
|
3
3
|
import ITText from "@/components/atoms/text/text";
|
|
4
|
+
import { useITAppearance } from "@/theme/theme-context";
|
|
4
5
|
|
|
5
6
|
const sizeMap = {
|
|
6
7
|
sm: { button: "px-2.5 py-1.5 text-[11px]", container: "p-0.5" },
|
|
@@ -31,16 +32,22 @@ export default function ITSegmentedControl({
|
|
|
31
32
|
disabled = false,
|
|
32
33
|
}: ITSegmentedControlProps) {
|
|
33
34
|
const { button, container } = sizeMap[size];
|
|
35
|
+
// Bento: borderless pill track with an ink pill for the selected option.
|
|
36
|
+
const bento = useITAppearance() === "bento";
|
|
34
37
|
|
|
35
38
|
return (
|
|
36
39
|
<div
|
|
37
40
|
role="group"
|
|
38
41
|
className={clsx(
|
|
39
|
-
"inline-flex
|
|
42
|
+
"inline-flex",
|
|
43
|
+
bento
|
|
44
|
+
? "rounded-full"
|
|
45
|
+
: "rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700",
|
|
40
46
|
container,
|
|
41
47
|
disabled && "opacity-50 cursor-not-allowed",
|
|
42
48
|
className
|
|
43
49
|
)}
|
|
50
|
+
style={bento ? { backgroundColor: "var(--it-bento-control-bg, #f1f5f9)" } : undefined}
|
|
44
51
|
>
|
|
45
52
|
{options.map((opt) => {
|
|
46
53
|
const isActive = opt.value === value;
|
|
@@ -53,12 +60,22 @@ export default function ITSegmentedControl({
|
|
|
53
60
|
disabled={disabled}
|
|
54
61
|
className={clsx(
|
|
55
62
|
button,
|
|
56
|
-
"
|
|
57
|
-
|
|
58
|
-
? "
|
|
59
|
-
:
|
|
63
|
+
"font-semibold transition-all flex items-center gap-1.5",
|
|
64
|
+
bento
|
|
65
|
+
? "rounded-full border border-transparent"
|
|
66
|
+
: isActive
|
|
67
|
+
? "rounded-lg bg-white dark:bg-slate-700 text-slate-800 dark:text-white shadow-sm border border-slate-200 dark:border-slate-600"
|
|
68
|
+
: "rounded-lg text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300 border border-transparent",
|
|
60
69
|
disabled && "pointer-events-none"
|
|
61
70
|
)}
|
|
71
|
+
style={
|
|
72
|
+
bento
|
|
73
|
+
? {
|
|
74
|
+
backgroundColor: isActive ? "var(--it-bento-ink-bg, #0f172a)" : "transparent",
|
|
75
|
+
color: isActive ? "var(--it-bento-ink-text, #ffffff)" : "var(--it-bento-control-text, #475569)",
|
|
76
|
+
}
|
|
77
|
+
: undefined
|
|
78
|
+
}
|
|
62
79
|
>
|
|
63
80
|
{opt.icon && <span>{opt.icon}</span>}
|
|
64
81
|
<ITText as="span">{opt.label}</ITText>
|
|
@@ -21,4 +21,8 @@ export interface ITTextareaProps {
|
|
|
21
21
|
maxLength?: number;
|
|
22
22
|
/** Resize behavior of the textarea: "none" | "vertical" | "horizontal" | "both". */
|
|
23
23
|
resize?: "none" | "vertical" | "horizontal" | "both";
|
|
24
|
+
/** Keydown handler (e.g. Enter to send in a chat composer). */
|
|
25
|
+
onKeyDown?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
26
|
+
/** Autofocus the textarea. */
|
|
27
|
+
autoFocus?: boolean;
|
|
24
28
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
|
+
import { forwardRef } from "react";
|
|
2
3
|
import { ITTextareaProps } from "./textarea.props";
|
|
3
4
|
import { inputLabel, inputError } from "@/utils/styles";
|
|
4
5
|
import ITText from "@/components/atoms/text/text";
|
|
@@ -26,7 +27,7 @@ const resizeMap = {
|
|
|
26
27
|
* error={bio.length > 500 ? "Max 500 characters" : undefined}
|
|
27
28
|
* />
|
|
28
29
|
*/
|
|
29
|
-
|
|
30
|
+
const ITTextarea = forwardRef<HTMLTextAreaElement, ITTextareaProps>(function ITTextarea({
|
|
30
31
|
value,
|
|
31
32
|
onChange,
|
|
32
33
|
label,
|
|
@@ -38,7 +39,9 @@ export default function ITTextarea({
|
|
|
38
39
|
name,
|
|
39
40
|
maxLength,
|
|
40
41
|
resize = "vertical",
|
|
41
|
-
|
|
42
|
+
onKeyDown,
|
|
43
|
+
autoFocus = false,
|
|
44
|
+
}, ref) {
|
|
42
45
|
return (
|
|
43
46
|
<div className={clsx("flex flex-col gap-1.5", className)}>
|
|
44
47
|
{label && (
|
|
@@ -47,6 +50,7 @@ export default function ITTextarea({
|
|
|
47
50
|
</ITText>
|
|
48
51
|
)}
|
|
49
52
|
<textarea
|
|
53
|
+
ref={ref}
|
|
50
54
|
id={name}
|
|
51
55
|
name={name}
|
|
52
56
|
value={value}
|
|
@@ -55,6 +59,8 @@ export default function ITTextarea({
|
|
|
55
59
|
rows={rows}
|
|
56
60
|
disabled={disabled}
|
|
57
61
|
maxLength={maxLength}
|
|
62
|
+
onKeyDown={onKeyDown}
|
|
63
|
+
autoFocus={autoFocus}
|
|
58
64
|
className={clsx(
|
|
59
65
|
"w-full border border-solid transition-all duration-200 rounded-lg px-3 py-2 text-sm outline-none",
|
|
60
66
|
"focus:ring-2",
|
|
@@ -68,4 +74,8 @@ export default function ITTextarea({
|
|
|
68
74
|
{error && <ITText as="span" className={inputError}>{error}</ITText>}
|
|
69
75
|
</div>
|
|
70
76
|
);
|
|
71
|
-
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
ITTextarea.displayName = "ITTextarea";
|
|
80
|
+
|
|
81
|
+
export default ITTextarea;
|
|
@@ -1,8 +1,22 @@
|
|
|
1
|
+
/** Pastel tint applied to a card through the `--color-<tone>-soft` token. */
|
|
2
|
+
export type ITCardTone =
|
|
3
|
+
| "primary"
|
|
4
|
+
| "secondary"
|
|
5
|
+
| "ternary"
|
|
6
|
+
| "success"
|
|
7
|
+
| "info"
|
|
8
|
+
| "warning"
|
|
9
|
+
| "danger";
|
|
10
|
+
|
|
1
11
|
export interface ITCardProps {
|
|
12
|
+
/** Pastel tile: tints the card background with the `--color-<tone>-soft` token of the palette, overriding the default white surface. In `soft`/`flat` appearances it also uses `--color-<tone>-soft-border` for the border; in `bento` the card stays borderless. Works in every appearance and in dark mode. Omit it for the regular surface. */
|
|
13
|
+
tone?: ITCardTone;
|
|
2
14
|
/** Click handler. When provided, the card becomes interactive with hover shadow effect. */
|
|
3
15
|
onClick?: () => void;
|
|
4
16
|
/** Card title displayed in the header area. */
|
|
5
17
|
title?: string;
|
|
18
|
+
/** Optional actions rendered at the right of the title row (e.g. a play button). */
|
|
19
|
+
headerActions?: React.ReactNode;
|
|
6
20
|
/** Image source URL displayed at the top of the card. */
|
|
7
21
|
image?: string;
|
|
8
22
|
/** Alt text for the card image. @default "Card Image" */
|
|
@@ -2,11 +2,12 @@ import clsx from "clsx";
|
|
|
2
2
|
import { ITCardProps } from "./card.props";
|
|
3
3
|
import { useState } from "react";
|
|
4
4
|
import ITText from "@/components/atoms/text/text";
|
|
5
|
-
import {
|
|
5
|
+
import { useITAppearance } from "@/theme/theme-context";
|
|
6
6
|
import { useITTranslation } from "@/i18n";
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* Versatile card container with optional image, title, body content, and action footer. Supports interactive hover states
|
|
9
|
+
* Versatile card container with optional image, title, body content, and action footer. Supports interactive hover states
|
|
10
|
+
* and pastel `tone` tiles. Follows the provider `appearance` (`soft`, `flat`, `bento`).
|
|
10
11
|
*
|
|
11
12
|
* @example
|
|
12
13
|
* <ITCard title="Welcome" image="/hero.jpg" actions={<ITButton label="Learn more" />}>
|
|
@@ -14,12 +15,17 @@ import { useITTranslation } from "@/i18n";
|
|
|
14
15
|
* </ITCard>
|
|
15
16
|
*
|
|
16
17
|
* @example
|
|
18
|
+
* <ITCard tone="success" title="Revenue">+12.4%</ITCard>
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
17
21
|
* <ITCard onClick={() => {}} className="max-w-sm">
|
|
18
22
|
* Clickable card with hover shadow.
|
|
19
23
|
* </ITCard>
|
|
20
24
|
*/
|
|
21
25
|
export default function ITCard({
|
|
22
26
|
title,
|
|
27
|
+
tone,
|
|
28
|
+
headerActions,
|
|
23
29
|
image,
|
|
24
30
|
alt,
|
|
25
31
|
children,
|
|
@@ -33,41 +39,66 @@ export default function ITCard({
|
|
|
33
39
|
}: ITCardProps) {
|
|
34
40
|
const { t } = useITTranslation();
|
|
35
41
|
const [isHovered, setIsHovered] = useState(false);
|
|
36
|
-
const
|
|
42
|
+
const appearance = useITAppearance();
|
|
43
|
+
const bento = appearance === "bento";
|
|
44
|
+
const flat = appearance === "flat";
|
|
37
45
|
const resolvedAlt = alt ?? t("card.imageAlt");
|
|
38
46
|
|
|
39
47
|
const containerStyle: React.CSSProperties = {
|
|
40
48
|
backgroundColor: "var(--it-card-bg, #ffffff)",
|
|
41
|
-
borderColor:
|
|
42
|
-
? "
|
|
43
|
-
:
|
|
44
|
-
|
|
49
|
+
borderColor: bento
|
|
50
|
+
? "transparent"
|
|
51
|
+
: flat
|
|
52
|
+
? "var(--it-card-flat-border, var(--color-secondary-200))"
|
|
53
|
+
: "var(--it-card-border, rgba(15, 23, 42, 0.06))",
|
|
54
|
+
borderWidth: bento ? "0px" : "1px",
|
|
45
55
|
borderStyle: "solid",
|
|
46
|
-
borderRadius:
|
|
47
|
-
? "var(--it-card-radius, var(--radius-
|
|
48
|
-
:
|
|
56
|
+
borderRadius: bento
|
|
57
|
+
? "var(--it-card-radius, var(--it-bento-card-radius, var(--radius-2xl)))"
|
|
58
|
+
: flat
|
|
59
|
+
? "var(--it-card-radius, var(--radius-lg))"
|
|
60
|
+
: "var(--it-card-radius, var(--radius-2xl))",
|
|
49
61
|
// Soft depth: every card floats a little at rest; interactive cards
|
|
50
62
|
// gain visual weight on hover instead of appearing flat until clicked.
|
|
51
63
|
// Flat appearance: the hairline border carries the surface, so a card
|
|
52
64
|
// only gets a shadow as hover feedback when it is clickable.
|
|
53
|
-
|
|
65
|
+
// Bento: borderless, a diffuse shadow at rest and a stronger one on hover.
|
|
66
|
+
boxShadow: bento
|
|
54
67
|
? onClick && isHovered
|
|
55
|
-
? "var(--shadow-
|
|
56
|
-
: "
|
|
57
|
-
:
|
|
58
|
-
? isHovered
|
|
59
|
-
? "var(--shadow-
|
|
60
|
-
: "
|
|
61
|
-
:
|
|
68
|
+
? "var(--it-bento-card-shadow-hover, var(--shadow-lg))"
|
|
69
|
+
: "var(--it-bento-card-shadow, var(--shadow-sm))"
|
|
70
|
+
: flat
|
|
71
|
+
? onClick && isHovered
|
|
72
|
+
? "var(--shadow-sm)"
|
|
73
|
+
: "none"
|
|
74
|
+
: onClick
|
|
75
|
+
? isHovered
|
|
76
|
+
? "var(--shadow-lg)"
|
|
77
|
+
: "var(--shadow-sm)"
|
|
78
|
+
: "var(--shadow-sm)",
|
|
62
79
|
transition: onClick ? "box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out" : "none",
|
|
63
|
-
transform: onClick && isHovered && !flat ? "translateY(-2px)" : "translateY(0)",
|
|
80
|
+
transform: onClick && isHovered && !flat && !bento ? "translateY(-2px)" : "translateY(0)",
|
|
64
81
|
cursor: onClick ? "pointer" : "default",
|
|
65
82
|
};
|
|
66
83
|
|
|
84
|
+
// Pastel tile: the soft token is translucent, so it is layered over the
|
|
85
|
+
// regular surface (opaque tile, correct in light and dark).
|
|
86
|
+
if (tone) {
|
|
87
|
+
const soft = `var(--color-${tone}-soft)`;
|
|
88
|
+
containerStyle.backgroundImage = `linear-gradient(${soft}, ${soft})`;
|
|
89
|
+
if (!bento) containerStyle.borderColor = `var(--color-${tone}-soft-border)`;
|
|
90
|
+
}
|
|
91
|
+
|
|
67
92
|
const bodyStyle: React.CSSProperties = {
|
|
68
|
-
padding: "1.25rem",
|
|
93
|
+
padding: bento ? "var(--it-bento-card-padding, 1.5rem)" : "1.25rem",
|
|
69
94
|
};
|
|
70
95
|
|
|
96
|
+
const titleTypography = bento
|
|
97
|
+
? "text-lg font-bold"
|
|
98
|
+
: flat
|
|
99
|
+
? "text-base font-semibold"
|
|
100
|
+
: "text-xl font-semibold";
|
|
101
|
+
|
|
71
102
|
return (
|
|
72
103
|
<div
|
|
73
104
|
onClick={onClick}
|
|
@@ -97,14 +128,26 @@ export default function ITCard({
|
|
|
97
128
|
)}
|
|
98
129
|
|
|
99
130
|
<div className={clsx(contentClassName)} style={bodyStyle}>
|
|
100
|
-
{
|
|
131
|
+
{headerActions ? (
|
|
132
|
+
<div className="mb-2 flex items-center justify-between gap-3">
|
|
133
|
+
{title && (
|
|
134
|
+
<ITText
|
|
135
|
+
as="h3"
|
|
136
|
+
className={clsx(titleTypography, titleClassName)}
|
|
137
|
+
>
|
|
138
|
+
{title}
|
|
139
|
+
</ITText>
|
|
140
|
+
)}
|
|
141
|
+
{headerActions}
|
|
142
|
+
</div>
|
|
143
|
+
) : title ? (
|
|
101
144
|
<ITText
|
|
102
145
|
as="h3"
|
|
103
|
-
className={clsx(flat ? "
|
|
146
|
+
className={clsx(titleTypography, flat ? "mb-3" : bento ? "mb-4" : "mb-2", titleClassName)}
|
|
104
147
|
>
|
|
105
148
|
{title}
|
|
106
149
|
</ITText>
|
|
107
|
-
)}
|
|
150
|
+
) : null}
|
|
108
151
|
<ITText as="div" className="text-secondary-600">{children}</ITText>
|
|
109
152
|
</div>
|
|
110
153
|
{actions && (
|
|
@@ -70,6 +70,8 @@ export default function ITChipInput({
|
|
|
70
70
|
style.alignItems = "center";
|
|
71
71
|
style.gap = "0.375rem";
|
|
72
72
|
style.minHeight = "2.5rem";
|
|
73
|
+
// Multi-line control: bento rounds it with a softer radius than the single-line pill.
|
|
74
|
+
style.borderRadius = "var(--it-bento-field-radius, var(--radius-md))";
|
|
73
75
|
|
|
74
76
|
if (disabled) {
|
|
75
77
|
style.backgroundColor = inputTheme.disabled?.backgroundColor || style.backgroundColor;
|
|
@@ -19,6 +19,10 @@ export interface ITLineBarChartProps {
|
|
|
19
19
|
lineColor?: string;
|
|
20
20
|
/** Color de las barras. */
|
|
21
21
|
barColor?: string;
|
|
22
|
+
/** Muestra la serie de línea. @default true */
|
|
23
|
+
showLine?: boolean;
|
|
24
|
+
/** Muestra la serie de barras. @default true */
|
|
25
|
+
showBars?: boolean;
|
|
22
26
|
height?: number;
|
|
23
27
|
/** Formatea los valores del tooltip; por defecto número con separadores. */
|
|
24
28
|
formatValue?: (value: number) => string;
|
|
@@ -16,6 +16,8 @@ export function ITLineBarChart({
|
|
|
16
16
|
barLabel,
|
|
17
17
|
lineColor = "#10b981",
|
|
18
18
|
barColor = "#3b82f6",
|
|
19
|
+
showLine = true,
|
|
20
|
+
showBars = true,
|
|
19
21
|
height = 240,
|
|
20
22
|
formatValue,
|
|
21
23
|
formatAxis,
|
|
@@ -40,8 +42,12 @@ export function ITLineBarChart({
|
|
|
40
42
|
const plotHeight = height - padding.top - padding.bottom;
|
|
41
43
|
|
|
42
44
|
const max = useMemo(
|
|
43
|
-
() =>
|
|
44
|
-
|
|
45
|
+
() =>
|
|
46
|
+
Math.max(
|
|
47
|
+
...data.flatMap((d) => [showLine ? d.line : 0, showBars ? d.bar : 0]),
|
|
48
|
+
1
|
|
49
|
+
),
|
|
50
|
+
[data, showLine, showBars]
|
|
45
51
|
);
|
|
46
52
|
const gridValues = [0, 0.25, 0.5, 0.75, 1].map((f) => max * f);
|
|
47
53
|
const step = data.length > 0 ? plotWidth / data.length : plotWidth;
|
|
@@ -103,21 +109,22 @@ export function ITLineBarChart({
|
|
|
103
109
|
</g>
|
|
104
110
|
))}
|
|
105
111
|
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
{showBars &&
|
|
113
|
+
data.map((d, i) => (
|
|
114
|
+
<rect
|
|
115
|
+
key={`bar-${d.label}`}
|
|
116
|
+
x={x(i) - barWidth / 2}
|
|
117
|
+
y={y(d.bar)}
|
|
118
|
+
width={barWidth}
|
|
119
|
+
height={Math.max(padding.top + plotHeight - y(d.bar), 1)}
|
|
120
|
+
rx={3}
|
|
121
|
+
fill={barColor}
|
|
122
|
+
opacity={hover === null || hover === i ? 0.85 : 0.35}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
118
125
|
|
|
119
|
-
{areaPath && <path d={areaPath} fill={`url(#area-${gradientId})`} />}
|
|
120
|
-
{data.length > 0 && (
|
|
126
|
+
{showLine && areaPath && <path d={areaPath} fill={`url(#area-${gradientId})`} />}
|
|
127
|
+
{showLine && data.length > 0 && (
|
|
121
128
|
<polyline
|
|
122
129
|
points={linePoints}
|
|
123
130
|
fill="none"
|
|
@@ -127,17 +134,18 @@ export function ITLineBarChart({
|
|
|
127
134
|
strokeLinejoin="round"
|
|
128
135
|
/>
|
|
129
136
|
)}
|
|
130
|
-
{
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
{showLine &&
|
|
138
|
+
data.map((d, i) => (
|
|
139
|
+
<circle
|
|
140
|
+
key={`dot-${d.label}`}
|
|
141
|
+
cx={x(i)}
|
|
142
|
+
cy={y(d.line)}
|
|
143
|
+
r={hover === i ? 4.5 : 3}
|
|
144
|
+
fill="#ffffff"
|
|
145
|
+
stroke={lineColor}
|
|
146
|
+
strokeWidth={2}
|
|
147
|
+
/>
|
|
148
|
+
))}
|
|
141
149
|
|
|
142
150
|
{data.map((d, i) => (
|
|
143
151
|
<g key={`hit-${d.label}`} onMouseEnter={() => setHover(i)}>
|
|
@@ -157,24 +165,32 @@ export function ITLineBarChart({
|
|
|
157
165
|
role="status"
|
|
158
166
|
>
|
|
159
167
|
<p className="text-[10px] font-semibold" style={{ color: "#e2e8f0" }}>{active.label}</p>
|
|
160
|
-
|
|
161
|
-
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
{showLine && (
|
|
169
|
+
<p className="text-[11px] tabular-nums" style={{ color: lineColor }}>
|
|
170
|
+
{resolvedLineLabel}: {resolvedFormatValue(active.line)}
|
|
171
|
+
</p>
|
|
172
|
+
)}
|
|
173
|
+
{showBars && (
|
|
174
|
+
<p className="text-[11px] tabular-nums" style={{ color: "#93c5fd" }}>
|
|
175
|
+
{resolvedBarLabel}: {resolvedFormatValue(active.bar)}
|
|
176
|
+
</p>
|
|
177
|
+
)}
|
|
166
178
|
</div>
|
|
167
179
|
)}
|
|
168
180
|
|
|
169
181
|
<ul className="flex flex-wrap items-center gap-4 text-[11px]" style={{ color: "#475569" }}>
|
|
170
|
-
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
{showLine && (
|
|
183
|
+
<li>
|
|
184
|
+
<span className="h-2.5 w-2.5 rounded-sm" style={{ background: lineColor }} aria-hidden />
|
|
185
|
+
{resolvedLineLabel}
|
|
186
|
+
</li>
|
|
187
|
+
)}
|
|
188
|
+
{showBars && (
|
|
189
|
+
<li>
|
|
190
|
+
<span className="h-2.5 w-2.5 rounded-sm" style={{ background: barColor }} aria-hidden />
|
|
191
|
+
{resolvedBarLabel}
|
|
192
|
+
</li>
|
|
193
|
+
)}
|
|
178
194
|
</ul>
|
|
179
195
|
</div>
|
|
180
196
|
);
|
|
@@ -127,6 +127,8 @@ export default function ITMultiSelect({
|
|
|
127
127
|
style.alignItems = "center";
|
|
128
128
|
style.gap = "0.375rem";
|
|
129
129
|
style.minHeight = "2.5rem";
|
|
130
|
+
// Multi-line control: bento rounds it with a softer radius than the single-line pill.
|
|
131
|
+
style.borderRadius = "var(--it-bento-field-radius, var(--radius-md))";
|
|
130
132
|
|
|
131
133
|
if (disabled) {
|
|
132
134
|
style.backgroundColor = inputTheme.disabled?.backgroundColor || style.backgroundColor;
|