@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.
Files changed (82) hide show
  1. package/dist/index.cjs +1845 -721
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.cts +386 -15
  5. package/dist/index.d.ts +386 -15
  6. package/dist/index.js +1532 -418
  7. package/dist/index.js.map +1 -1
  8. package/dist/layered.css +1 -1
  9. package/package.json +4 -2
  10. package/src/App.tsx +6 -8
  11. package/src/components/atoms/button/button.tsx +7 -0
  12. package/src/components/atoms/flex/flex.props.ts +3 -1
  13. package/src/components/atoms/flex/flex.tsx +11 -3
  14. package/src/components/atoms/input/input.tsx +5 -0
  15. package/src/components/atoms/segmented-control/segmented-control.tsx +22 -5
  16. package/src/components/atoms/textarea/textarea.props.ts +4 -0
  17. package/src/components/atoms/textarea/textarea.tsx +13 -3
  18. package/src/components/molecules/card/card.props.ts +14 -0
  19. package/src/components/molecules/card/card.tsx +66 -23
  20. package/src/components/molecules/chip-input/chip-input.tsx +2 -0
  21. package/src/components/molecules/line-bar-chart/line-bar-chart.props.ts +4 -0
  22. package/src/components/molecules/line-bar-chart/line-bar-chart.tsx +57 -41
  23. package/src/components/molecules/multi-select/multi-select.tsx +2 -0
  24. package/src/components/molecules/tabs/tabs.tsx +22 -8
  25. package/src/components/organisms/dropfile/dropfile.props.ts +8 -2
  26. package/src/components/organisms/dropfile/dropfile.tsx +17 -0
  27. package/src/components/organisms/page-header/page-header.tsx +22 -6
  28. package/src/components/organisms/sidebar/sidebar.tsx +8 -5
  29. package/src/components/organisms/topbar/topbar.props.ts +2 -0
  30. package/src/components/organisms/topbar/topbar.tsx +56 -34
  31. package/src/components/templates/layout/layout.tsx +3 -2
  32. package/src/components/theme-provider/themeProvider.props.ts +1 -1
  33. package/src/components/theme-provider/themeProvider.tsx +54 -1
  34. package/src/i18n/locales/en.ts +7 -0
  35. package/src/i18n/locales/es.ts +7 -0
  36. package/src/index.ts +35 -7
  37. package/src/json-renderer/actions/effects.test.tsx +115 -0
  38. package/src/json-renderer/actions/effects.ts +291 -0
  39. package/src/json-renderer/actions/effectsChain.test.ts +117 -0
  40. package/src/json-renderer/binding/binding.test.tsx +168 -0
  41. package/src/json-renderer/binding/resolveValue.ts +193 -0
  42. package/src/json-renderer/catalog/axzyCatalog.ts +5 -0
  43. package/src/json-renderer/estado-acciones.test.tsx +166 -0
  44. package/src/json-renderer/form/FormNode.tsx +92 -0
  45. package/src/json-renderer/form/form.test.tsx +148 -0
  46. package/src/json-renderer/form/formLength.test.tsx +99 -0
  47. package/src/json-renderer/form/rules.ts +162 -0
  48. package/src/json-renderer/index.ts +21 -0
  49. package/src/json-renderer/kernel/SchemaRenderer.tsx +83 -7
  50. package/src/json-renderer/kernel/capabilities.ts +30 -0
  51. package/src/json-renderer/kernel/renderNode.tsx +256 -3
  52. package/src/json-renderer/kernel/schemaRenderer.props.ts +29 -3
  53. package/src/json-renderer/kernel/slots.test.tsx +104 -0
  54. package/src/json-renderer/kernel/slots.ts +34 -0
  55. package/src/json-renderer/kernel/types.ts +4 -0
  56. package/src/json-renderer/routing/match.ts +82 -0
  57. package/src/json-renderer/routing/navigation.ts +71 -0
  58. package/src/json-renderer/routing/routes.ts +179 -0
  59. package/src/json-renderer/routing/routing.test.ts +147 -0
  60. package/src/json-renderer/seguridad.test.tsx +299 -0
  61. package/src/json-renderer/shared/errors.ts +1 -0
  62. package/src/json-renderer/stream/tolerantParse.ts +5 -0
  63. package/src/json-renderer/stream/useSchemaStream.test.tsx +143 -0
  64. package/src/json-renderer/stream/useSchemaStream.ts +227 -0
  65. package/src/json-renderer/validation/jsonSchema.test.ts +105 -0
  66. package/src/json-renderer/validation/jsonSchema.ts +317 -0
  67. package/src/json-renderer/validation/validate.ts +17 -0
  68. package/src/sandbox/axzyConfig.test.ts +4 -0
  69. package/src/sandbox/axzyConfig.ts +1 -1
  70. package/src/sandbox/i18n.ts +44 -0
  71. package/src/sandbox/useHashRoute.ts +2 -1
  72. package/src/showcases/DemoJsonDashboard.tsx +218 -103
  73. package/src/showcases/DemoJsonDocs.tsx +11 -11
  74. package/src/showcases/FeedbackShowcases.tsx +2 -1
  75. package/src/showcases/JsonScreenShowcase.tsx +43 -53
  76. package/src/showcases/StructureShowcases.tsx +17 -2
  77. package/src/showcases/demoApp/demoJson.test.tsx +241 -0
  78. package/src/showcases/demoApp/routes.test.ts +63 -0
  79. package/src/showcases/demoApp/schemas.test.ts +6 -17
  80. package/src/theme/theme-context.ts +10 -2
  81. package/src/theme/theme.ts +1 -1
  82. 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.3",
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, { DEMO_APP_PAGES, type DemoAppPage } from "./showcases/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
- page={(DEMO_APP_PAGES.includes((route.item ?? "inicio") as DemoAppPage)
381
- ? route.item
382
- : "inicio") as DemoAppPage}
383
- onNavigate={(pagina) => {
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 ?? "inicio"}`;
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
- export default function ITFlex({
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
- }: ITFlexProps) {
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 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700",
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
- "rounded-lg font-semibold transition-all flex items-center gap-1.5",
57
- isActive
58
- ? "bg-white dark:bg-slate-700 text-slate-800 dark:text-white shadow-sm border border-slate-200 dark:border-slate-600"
59
- : "text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-300 border border-transparent",
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
- export default function ITTextarea({
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
- }: ITTextareaProps) {
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 { useITFlatAppearance } from "@/theme/theme-context";
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 flat = useITFlatAppearance();
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: flat
42
- ? "var(--it-card-flat-border, var(--color-secondary-200))"
43
- : "var(--it-card-border, rgba(15, 23, 42, 0.06))",
44
- borderWidth: "1px",
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: flat
47
- ? "var(--it-card-radius, var(--radius-lg))"
48
- : "var(--it-card-radius, var(--radius-2xl))",
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
- boxShadow: flat
65
+ // Bento: borderless, a diffuse shadow at rest and a stronger one on hover.
66
+ boxShadow: bento
54
67
  ? onClick && isHovered
55
- ? "var(--shadow-sm)"
56
- : "none"
57
- : onClick
58
- ? isHovered
59
- ? "var(--shadow-lg)"
60
- : "var(--shadow-sm)"
61
- : "var(--shadow-sm)",
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
- {title && (
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 ? "text-base font-semibold mb-3" : "text-xl font-semibold mb-2", titleClassName)}
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
- () => Math.max(...data.flatMap((d) => [d.line, d.bar]), 1),
44
- [data]
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
- {data.map((d, i) => (
107
- <rect
108
- key={`bar-${d.label}`}
109
- x={x(i) - barWidth / 2}
110
- y={y(d.bar)}
111
- width={barWidth}
112
- height={Math.max(padding.top + plotHeight - y(d.bar), 1)}
113
- rx={3}
114
- fill={barColor}
115
- opacity={hover === null || hover === i ? 0.85 : 0.35}
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
- {data.map((d, i) => (
131
- <circle
132
- key={`dot-${d.label}`}
133
- cx={x(i)}
134
- cy={y(d.line)}
135
- r={hover === i ? 4.5 : 3}
136
- fill="#ffffff"
137
- stroke={lineColor}
138
- strokeWidth={2}
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
- <p className="text-[11px] tabular-nums" style={{ color: lineColor }}>
161
- {resolvedLineLabel}: {resolvedFormatValue(active.line)}
162
- </p>
163
- <p className="text-[11px] tabular-nums" style={{ color: "#93c5fd" }}>
164
- {resolvedBarLabel}: {resolvedFormatValue(active.bar)}
165
- </p>
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
- <li>
171
- <span className="h-2.5 w-2.5 rounded-sm" style={{ background: lineColor }} aria-hidden />
172
- {resolvedLineLabel}
173
- </li>
174
- <li>
175
- <span className="h-2.5 w-2.5 rounded-sm" style={{ background: barColor }} aria-hidden />
176
- {resolvedBarLabel}
177
- </li>
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;