@axzydev/axzy_ui_system 1.7.4 → 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 (34) hide show
  1. package/dist/index.cjs +204 -107
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.cts +22 -5
  5. package/dist/index.d.ts +22 -5
  6. package/dist/index.js +200 -104
  7. package/dist/index.js.map +1 -1
  8. package/dist/layered.css +1 -1
  9. package/package.json +1 -1
  10. package/src/components/atoms/button/button.tsx +7 -0
  11. package/src/components/atoms/input/input.tsx +5 -0
  12. package/src/components/atoms/segmented-control/segmented-control.tsx +22 -5
  13. package/src/components/molecules/card/card.props.ts +12 -0
  14. package/src/components/molecules/card/card.tsx +52 -22
  15. package/src/components/molecules/chip-input/chip-input.tsx +2 -0
  16. package/src/components/molecules/multi-select/multi-select.tsx +2 -0
  17. package/src/components/molecules/tabs/tabs.tsx +22 -8
  18. package/src/components/organisms/page-header/page-header.tsx +22 -6
  19. package/src/components/organisms/sidebar/sidebar.tsx +8 -5
  20. package/src/components/organisms/topbar/topbar.props.ts +2 -0
  21. package/src/components/organisms/topbar/topbar.tsx +56 -34
  22. package/src/components/templates/layout/layout.tsx +3 -2
  23. package/src/components/theme-provider/themeProvider.props.ts +1 -1
  24. package/src/components/theme-provider/themeProvider.tsx +54 -1
  25. package/src/index.ts +4 -4
  26. package/src/json-renderer/catalog/axzyCatalog.ts +5 -0
  27. package/src/sandbox/axzyConfig.test.ts +4 -0
  28. package/src/sandbox/axzyConfig.ts +1 -1
  29. package/src/sandbox/i18n.ts +6 -0
  30. package/src/showcases/FeedbackShowcases.tsx +2 -1
  31. package/src/showcases/StructureShowcases.tsx +17 -2
  32. package/src/theme/theme-context.ts +10 -2
  33. package/src/theme/theme.ts +1 -1
  34. 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.4",
7
+ "version": "1.8.0",
8
8
  "type": "module",
9
9
  "sideEffects": [
10
10
  "*.css"
@@ -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
  }
@@ -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>
@@ -1,4 +1,16 @@
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. */
@@ -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,16 @@ 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,
23
28
  headerActions,
24
29
  image,
25
30
  alt,
@@ -34,41 +39,66 @@ export default function ITCard({
34
39
  }: ITCardProps) {
35
40
  const { t } = useITTranslation();
36
41
  const [isHovered, setIsHovered] = useState(false);
37
- const flat = useITFlatAppearance();
42
+ const appearance = useITAppearance();
43
+ const bento = appearance === "bento";
44
+ const flat = appearance === "flat";
38
45
  const resolvedAlt = alt ?? t("card.imageAlt");
39
46
 
40
47
  const containerStyle: React.CSSProperties = {
41
48
  backgroundColor: "var(--it-card-bg, #ffffff)",
42
- borderColor: flat
43
- ? "var(--it-card-flat-border, var(--color-secondary-200))"
44
- : "var(--it-card-border, rgba(15, 23, 42, 0.06))",
45
- 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",
46
55
  borderStyle: "solid",
47
- borderRadius: flat
48
- ? "var(--it-card-radius, var(--radius-lg))"
49
- : "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))",
50
61
  // Soft depth: every card floats a little at rest; interactive cards
51
62
  // gain visual weight on hover instead of appearing flat until clicked.
52
63
  // Flat appearance: the hairline border carries the surface, so a card
53
64
  // only gets a shadow as hover feedback when it is clickable.
54
- boxShadow: flat
65
+ // Bento: borderless, a diffuse shadow at rest and a stronger one on hover.
66
+ boxShadow: bento
55
67
  ? onClick && isHovered
56
- ? "var(--shadow-sm)"
57
- : "none"
58
- : onClick
59
- ? isHovered
60
- ? "var(--shadow-lg)"
61
- : "var(--shadow-sm)"
62
- : "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)",
63
79
  transition: onClick ? "box-shadow 0.2s ease-in-out, transform 0.2s ease-in-out" : "none",
64
- transform: onClick && isHovered && !flat ? "translateY(-2px)" : "translateY(0)",
80
+ transform: onClick && isHovered && !flat && !bento ? "translateY(-2px)" : "translateY(0)",
65
81
  cursor: onClick ? "pointer" : "default",
66
82
  };
67
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
+
68
92
  const bodyStyle: React.CSSProperties = {
69
- padding: "1.25rem",
93
+ padding: bento ? "var(--it-bento-card-padding, 1.5rem)" : "1.25rem",
70
94
  };
71
95
 
96
+ const titleTypography = bento
97
+ ? "text-lg font-bold"
98
+ : flat
99
+ ? "text-base font-semibold"
100
+ : "text-xl font-semibold";
101
+
72
102
  return (
73
103
  <div
74
104
  onClick={onClick}
@@ -103,7 +133,7 @@ export default function ITCard({
103
133
  {title && (
104
134
  <ITText
105
135
  as="h3"
106
- className={clsx(flat ? "text-base font-semibold" : "text-xl font-semibold", titleClassName)}
136
+ className={clsx(titleTypography, titleClassName)}
107
137
  >
108
138
  {title}
109
139
  </ITText>
@@ -113,7 +143,7 @@ export default function ITCard({
113
143
  ) : title ? (
114
144
  <ITText
115
145
  as="h3"
116
- 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)}
117
147
  >
118
148
  {title}
119
149
  </ITText>
@@ -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;
@@ -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;
@@ -2,10 +2,12 @@ import React, { useState } from 'react';
2
2
  import { ITTabsProps } from './tabs.props';
3
3
  import { clsx } from 'clsx';
4
4
  import ITText from "@/components/atoms/text/text";
5
+ import { useITAppearance } from "@/theme/theme-context";
5
6
 
6
7
  /**
7
8
  * Tabbed navigation component with "line" and "pill" visual variants.
8
9
  * Manages its own active state internally via a defaultActiveId.
10
+ * With `appearance="bento"` the "pill" variant becomes a rounded track with an ink active pill.
9
11
  *
10
12
  * @example
11
13
  * <ITTabs
@@ -25,6 +27,7 @@ const ITTabs: React.FC<ITTabsProps> = ({
25
27
  className = '',
26
28
  containerClassName = ''
27
29
  }) => {
30
+ const bento = useITAppearance() === "bento" && variant === 'pill';
28
31
  const [activeId, setActiveId] = useState(defaultActiveId || items[0]?.id);
29
32
 
30
33
  const handleTabClick = (id: string, disabled?: boolean) => {
@@ -40,9 +43,10 @@ const ITTabs: React.FC<ITTabsProps> = ({
40
43
  {/* HEADER */}
41
44
  <div className={clsx(
42
45
  "flex border-secondary-200 mb-4",
43
- variant === 'line' ? "border-b" : "gap-2 p-1 bg-secondary-100 rounded-lg w-fit",
46
+ variant === 'line' ? "border-b" : bento ? "gap-2 p-1 rounded-full w-fit" : "gap-2 p-1 bg-secondary-100 rounded-lg w-fit",
44
47
  className
45
- )}>
48
+ )}
49
+ style={bento ? { backgroundColor: "var(--it-bento-control-bg, #f1f5f9)", borderColor: "transparent" } : undefined}>
46
50
  {items.map((item) => {
47
51
  const isActive = item.id === activeId;
48
52
 
@@ -61,14 +65,24 @@ const ITTabs: React.FC<ITTabsProps> = ({
61
65
  : "border-transparent text-secondary-500 hover:text-secondary-700 hover:border-secondary-300",
62
66
  ],
63
67
  // PILL VARIANT
64
- variant === 'pill' && [
65
- "rounded-md",
66
- isActive
67
- ? "bg-white text-primary-600 shadow-sm"
68
- : "text-secondary-500 hover:text-secondary-700",
69
- ],
68
+ variant === 'pill' && (bento
69
+ ? "rounded-full"
70
+ : [
71
+ "rounded-md",
72
+ isActive
73
+ ? "bg-white text-primary-600 shadow-sm"
74
+ : "text-secondary-500 hover:text-secondary-700",
75
+ ]),
70
76
  item.disabled && "opacity-50 cursor-not-allowed"
71
77
  )}
78
+ style={
79
+ bento
80
+ ? {
81
+ backgroundColor: isActive ? "var(--it-bento-ink-bg, #0f172a)" : "transparent",
82
+ color: isActive ? "var(--it-bento-ink-text, #ffffff)" : "var(--it-bento-control-text, #475569)",
83
+ }
84
+ : undefined
85
+ }
72
86
  >
73
87
  {item.icon && <span className="w-4 h-4">{item.icon}</span>}
74
88
  <ITText as="span">{item.label}</ITText>
@@ -2,7 +2,7 @@ import { FaChevronLeft } from "react-icons/fa";
2
2
  import { ITPageHeaderProps } from "./page-header.props";
3
3
  import ITBreadcrumbs from "@/components/molecules/breadcrumbs/breadcrumbs";
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
  /**
@@ -33,7 +33,10 @@ export default function ITPageHeader({
33
33
  const { t } = useITTranslation();
34
34
  const showTopRow = breadcrumbs?.length || backAction;
35
35
  // Flat appearance: compact title, no decorative icon tile.
36
- const flat = useITFlatAppearance();
36
+ // Bento: no icon tile either, but a larger tight title and a borderless round back button.
37
+ const appearance = useITAppearance();
38
+ const bento = appearance === "bento";
39
+ const flat = appearance === "flat";
37
40
 
38
41
  return (
39
42
  <div className={`${className} space-y-3`}>
@@ -44,7 +47,16 @@ export default function ITPageHeader({
44
47
  {backAction && (
45
48
  <button
46
49
  onClick={backAction}
47
- className="flex items-center justify-center w-9 h-9 rounded-full border border-slate-200 dark:border-slate-700 text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:border-slate-300 dark:hover:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all duration-200 flex-shrink-0"
50
+ className={
51
+ bento
52
+ ? "flex items-center justify-center w-10 h-10 rounded-full hover:opacity-80 transition-opacity duration-200 flex-shrink-0"
53
+ : "flex items-center justify-center w-9 h-9 rounded-full border border-slate-200 dark:border-slate-700 text-slate-400 hover:text-slate-700 dark:hover:text-slate-200 hover:border-slate-300 dark:hover:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-800/60 transition-all duration-200 flex-shrink-0"
54
+ }
55
+ style={
56
+ bento
57
+ ? { backgroundColor: "var(--it-bento-control-bg, #f1f5f9)", color: "var(--it-bento-control-text, #475569)" }
58
+ : undefined
59
+ }
48
60
  aria-label={t("pageHeader.back")}
49
61
  >
50
62
  <FaChevronLeft size={14} />
@@ -65,7 +77,7 @@ export default function ITPageHeader({
65
77
  {/* Fila principal: icono + título + descripción + (acciones si no hay top row) */}
66
78
  <div className="flex items-start justify-between gap-6">
67
79
  <div className="flex items-start gap-4 min-w-0">
68
- {icon && !flat && (
80
+ {icon && !flat && !bento && (
69
81
  <div
70
82
  className="flex items-center justify-center w-12 h-12 rounded-full border border-slate-200 dark:border-slate-700 bg-slate-50/50 dark:bg-slate-800/50 text-slate-600 dark:text-slate-300 flex-shrink-0 transition-all duration-200 hover:border-slate-300 dark:hover:border-slate-500 hover:shadow-sm"
71
83
  style={iconColor ? { color: iconColor } : undefined}
@@ -77,7 +89,9 @@ export default function ITPageHeader({
77
89
  <ITText
78
90
  as="h1"
79
91
  className={
80
- flat
92
+ bento
93
+ ? "text-3xl font-bold text-slate-900 dark:text-white tracking-tighter leading-tight"
94
+ : flat
81
95
  ? "text-[1.625rem] font-bold text-slate-900 dark:text-white tracking-tight leading-tight"
82
96
  : "text-3xl font-bold text-slate-800 dark:text-white tracking-tight leading-tight"
83
97
  }
@@ -88,7 +102,9 @@ export default function ITPageHeader({
88
102
  <ITText
89
103
  as="p"
90
104
  className={
91
- flat
105
+ bento
106
+ ? "text-sm font-normal text-slate-500 dark:text-slate-400 mt-1.5 leading-relaxed"
107
+ : flat
92
108
  ? "text-sm font-normal text-slate-500 dark:text-slate-400 mt-0.5 leading-relaxed"
93
109
  : "text-sm font-light text-slate-500 dark:text-slate-400 mt-1 leading-relaxed"
94
110
  }
@@ -10,7 +10,7 @@ import {
10
10
  } from "./sidebar.props";
11
11
  import ITBadget from "@/components/atoms/badget/badget";
12
12
  import ITText from "@/components/atoms/text/text";
13
- import { useITFlatAppearance } from "@/theme/theme-context";
13
+ import { useITAppearance } from "@/theme/theme-context";
14
14
  import { useITTranslation } from "@/i18n";
15
15
 
16
16
  /** Narrows a submenu entry to a titled group. */
@@ -95,7 +95,10 @@ export default function ITSidebar({
95
95
  expandOnHover = true,
96
96
  }: ITSidebarProps) {
97
97
  // Flat appearance: plain surface, no glow/accent bar, narrower panel.
98
- const flat = useITFlatAppearance();
98
+ // Bento shares that structure; its tokens make the active item a neutral pill with bold ink text.
99
+ const appearance = useITAppearance();
100
+ const bento = appearance === "bento";
101
+ const flat = appearance === "flat" || bento;
99
102
  const { t } = useITTranslation();
100
103
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
101
104
  const [isHovering, setIsHovering] = useState(false);
@@ -202,7 +205,7 @@ export default function ITSidebar({
202
205
  color: subitem.isActive ? "var(--it-sidebar-active-color, var(--color-secondary-900))" : "var(--it-sidebar-label-color, var(--color-secondary-600))",
203
206
  backgroundColor: subitem.isActive ? "var(--it-sidebar-active-bg, var(--color-secondary-50))" : 'transparent',
204
207
  fontSize: flat ? '0.84rem' : '0.78rem',
205
- fontWeight: subitem.isActive ? 600 : 500,
208
+ fontWeight: subitem.isActive ? (bento ? 700 : 600) : 500,
206
209
  letterSpacing: '0.01em',
207
210
  marginLeft: subitemConnector === '|' ? '-1px' : '0',
208
211
  }}
@@ -365,7 +368,7 @@ export default function ITSidebar({
365
368
  style={{
366
369
  zIndex: 50,
367
370
  backgroundColor: "var(--it-sidebar-bg, rgba(255, 255, 255, 0.90))",
368
- borderRight: "1px solid var(--it-sidebar-border, var(--color-secondary-200))",
371
+ borderRight: bento ? "none" : "1px solid var(--it-sidebar-border, var(--color-secondary-200))",
369
372
  ...(flat ? {} : { WebkitBackdropFilter: 'blur(12px)', backdropFilter: 'blur(12px)' }),
370
373
  }}
371
374
  >
@@ -480,7 +483,7 @@ export default function ITSidebar({
480
483
  style={{
481
484
  color: active ? "var(--it-sidebar-active-color, #ffffff)" : "var(--it-sidebar-label-color, var(--color-secondary-300))",
482
485
  fontSize: flat ? '0.875rem' : '0.8rem',
483
- fontWeight: active ? '600' : '500'
486
+ fontWeight: active ? (bento ? '700' : '600') : '500'
484
487
  }}
485
488
  >
486
489
  {item.label}
@@ -7,6 +7,8 @@ export interface ITTopBarNavItem {
7
7
  label: string;
8
8
  /** Optional icon element rendered beside the label. */
9
9
  icon?: any;
10
+ /** Marks the item as the current one. Sets `aria-current="page"`; with `appearance="bento"` it renders as the ink pill (dark fill, white text), in the other appearances as a subtle highlight. @default false */
11
+ isActive?: boolean;
10
12
  /** Click handler for this navigation item (legacy, prefer onNavItemClick). */
11
13
  action: () => void;
12
14
  }
@@ -3,7 +3,7 @@ import useClickOutside from "@/hooks/useClickOutside";
3
3
  import { useRef, useState } from "react";
4
4
  import { ITTopBarProps } from "./topbar.props";
5
5
  import ITText from "@/components/atoms/text/text";
6
- import { useITFlatAppearance } from "@/theme/theme-context";
6
+ import { useITAppearance } from "@/theme/theme-context";
7
7
  import { useITTranslation } from "@/i18n";
8
8
 
9
9
  /**
@@ -73,7 +73,10 @@ export default function ITTopBar({
73
73
  showDesktopMenuButton = false,
74
74
  }: ITTopBarProps) {
75
75
  // Flat appearance: plain bar (no shadow/blur), initials avatar, name before it.
76
- const flat = useITFlatAppearance();
76
+ // Bento: same plain structure, blended with the canvas, pill nav and round icon buttons.
77
+ const appearance = useITAppearance();
78
+ const bento = appearance === "bento";
79
+ const flat = appearance === "flat" || bento;
77
80
  const { t } = useITTranslation();
78
81
  const initials = (userMenu?.userName ?? "")
79
82
  .trim()
@@ -81,6 +84,8 @@ export default function ITTopBar({
81
84
  .slice(0, 2)
82
85
  .map((word) => word.charAt(0).toUpperCase())
83
86
  .join("");
87
+ const userRestBg = bento ? "var(--it-bento-control-bg, #f1f5f9)" : "transparent";
88
+ const userHoverBg = bento ? "var(--it-bento-control-bg-hover, #e2e8f0)" : "var(--it-topbar-user-hover, #f1f5f9)";
84
89
  const [isUserMenuOpen, setIsUserMenuOpen] = useState(false);
85
90
  const userMenuRef = useRef<HTMLDivElement>(null);
86
91
 
@@ -90,8 +95,10 @@ export default function ITTopBar({
90
95
  <header
91
96
  className={`sticky top-0 z-40 transition-all duration-300 ${flat ? "" : "backdrop-blur-md"}`}
92
97
  style={{
93
- backgroundColor: "var(--it-topbar-bg, rgba(255, 255, 255, 0.9))",
94
- borderBottom: "1px solid var(--it-topbar-border, rgba(15, 23, 42, 0.06))",
98
+ backgroundColor: bento
99
+ ? "var(--it-layout-bg, #f4f4f6)"
100
+ : "var(--it-topbar-bg, rgba(255, 255, 255, 0.9))",
101
+ borderBottom: bento ? "none" : "1px solid var(--it-topbar-border, rgba(15, 23, 42, 0.06))",
95
102
  boxShadow: flat ? "none" : "var(--shadow-xs)",
96
103
  }}
97
104
  >
@@ -105,12 +112,13 @@ export default function ITTopBar({
105
112
  <button
106
113
  type="button"
107
114
  aria-label={t("common.menu")}
108
- className={`${showDesktopMenuButton ? "" : "lg:hidden"} p-2.5 -ml-2.5 rounded-xl transition-colors duration-200`}
115
+ className={`${showDesktopMenuButton ? "" : "lg:hidden"} ${bento ? "w-10 h-10 flex items-center justify-center rounded-full" : "p-2.5 -ml-2.5 rounded-xl"} transition-colors duration-200`}
109
116
  style={{
110
- color: "var(--it-topbar-icon, #64748b)",
117
+ color: bento ? "var(--it-bento-control-text, #475569)" : "var(--it-topbar-icon, #64748b)",
118
+ backgroundColor: bento ? "var(--it-bento-control-bg, #f1f5f9)" : undefined,
111
119
  }}
112
- onMouseEnter={(e) => e.currentTarget.style.backgroundColor = "var(--it-topbar-user-hover, #f1f5f9)"}
113
- onMouseLeave={(e) => e.currentTarget.style.backgroundColor = "transparent"}
120
+ onMouseEnter={(e) => e.currentTarget.style.backgroundColor = bento ? "var(--it-bento-control-bg-hover, #e2e8f0)" : "var(--it-topbar-user-hover, #f1f5f9)"}
121
+ onMouseLeave={(e) => e.currentTarget.style.backgroundColor = bento ? "var(--it-bento-control-bg, #f1f5f9)" : "transparent"}
114
122
  onClick={onToggleMobileMenu}
115
123
  >
116
124
  <FaBars className="w-[1.125rem] h-[1.125rem]" />
@@ -137,28 +145,42 @@ export default function ITTopBar({
137
145
 
138
146
  {/* Top Navigation Items (Desktop) */}
139
147
  {navItems && navItems.length > 0 && (
140
- <nav className="hidden md:flex ml-8 space-x-1 border-l pl-8" style={{ borderColor: "var(--it-topbar-border, rgba(15, 23, 42, 0.06))" }}>
141
- {navItems.map((item) => (
142
- <button
143
- key={item.id}
144
- onClick={() => onNavItemClick?.(item.id)}
145
- className="px-4 py-2 rounded-lg font-medium text-[0.9rem] transition-all duration-200 ease-[cubic-bezier(0.2,0,0,1)]"
146
- style={{ color: "var(--it-topbar-text, #475569)" }}
147
- onMouseEnter={(e) => {
148
- e.currentTarget.style.color = "var(--it-topbar-text, #0f172a)";
149
- e.currentTarget.style.backgroundColor = "var(--it-topbar-user-hover, #f1f5f9)";
150
- }}
151
- onMouseLeave={(e) => {
152
- e.currentTarget.style.color = "var(--it-topbar-text, #475569)";
153
- e.currentTarget.style.backgroundColor = "transparent";
154
- }}
155
- >
156
- <div className="flex items-center gap-2">
157
- {item.icon && <span className="opacity-70">{item.icon}</span>}
158
- <ITText as="span">{item.label}</ITText>
159
- </div>
160
- </button>
161
- ))}
148
+ <nav
149
+ className={bento ? "hidden md:flex ml-4 gap-2" : "hidden md:flex ml-8 space-x-1 border-l pl-8"}
150
+ style={bento ? undefined : { borderColor: "var(--it-topbar-border, rgba(15, 23, 42, 0.06))" }}
151
+ >
152
+ {navItems.map((item) => {
153
+ // Resting look per appearance; the active item is the ink pill in bento.
154
+ const restBg = bento
155
+ ? item.isActive ? "var(--it-bento-ink-bg, #0f172a)" : "var(--it-bento-control-bg, #f1f5f9)"
156
+ : item.isActive ? "var(--it-topbar-user-hover, #f1f5f9)" : "transparent";
157
+ const restColor = bento
158
+ ? item.isActive ? "var(--it-bento-ink-text, #ffffff)" : "var(--it-bento-control-text, #475569)"
159
+ : "var(--it-topbar-text, #475569)";
160
+ return (
161
+ <button
162
+ key={item.id}
163
+ onClick={() => onNavItemClick?.(item.id)}
164
+ aria-current={item.isActive ? "page" : undefined}
165
+ className={`px-4 py-2 ${bento ? "rounded-full" : "rounded-lg"} font-medium text-[0.9rem] transition-all duration-200 ease-[cubic-bezier(0.2,0,0,1)]`}
166
+ style={{ color: restColor, backgroundColor: restBg }}
167
+ onMouseEnter={(e) => {
168
+ if (bento && item.isActive) return;
169
+ e.currentTarget.style.color = bento ? "var(--it-bento-control-text, #0f172a)" : "var(--it-topbar-text, #0f172a)";
170
+ e.currentTarget.style.backgroundColor = bento ? "var(--it-bento-control-bg-hover, #e2e8f0)" : "var(--it-topbar-user-hover, #f1f5f9)";
171
+ }}
172
+ onMouseLeave={(e) => {
173
+ e.currentTarget.style.color = restColor;
174
+ e.currentTarget.style.backgroundColor = restBg;
175
+ }}
176
+ >
177
+ <div className="flex items-center gap-2">
178
+ {item.icon && <span className="opacity-70">{item.icon}</span>}
179
+ <ITText as="span">{item.label}</ITText>
180
+ </div>
181
+ </button>
182
+ );
183
+ })}
162
184
  </nav>
163
185
  )}
164
186
  </div>
@@ -184,15 +206,15 @@ export default function ITTopBar({
184
206
  <div className="relative">
185
207
  <button
186
208
  type="button"
187
- className={`flex items-center gap-3 rounded-full py-1.5 transition-all duration-200 ease-[cubic-bezier(0.2,0,0,1)] border border-transparent hover:border-secondary-200 ${flat ? "flex-row-reverse pl-4 pr-2" : "pl-2 pr-4"}`}
209
+ className={`flex items-center gap-3 rounded-full py-1.5 transition-all duration-200 ease-[cubic-bezier(0.2,0,0,1)] border border-transparent ${bento ? "" : "hover:border-secondary-200"} ${flat ? "flex-row-reverse pl-4 pr-2" : "pl-2 pr-4"}`}
188
210
  style={{
189
- backgroundColor: isUserMenuOpen ? "var(--it-topbar-user-hover, #f1f5f9)" : "transparent",
211
+ backgroundColor: isUserMenuOpen ? userHoverBg : userRestBg,
190
212
  }}
191
213
  onMouseEnter={(e) => {
192
- if (!isUserMenuOpen) e.currentTarget.style.backgroundColor = "var(--it-topbar-user-hover, #f1f5f9)";
214
+ if (!isUserMenuOpen) e.currentTarget.style.backgroundColor = userHoverBg;
193
215
  }}
194
216
  onMouseLeave={(e) => {
195
- if (!isUserMenuOpen) e.currentTarget.style.backgroundColor = "transparent";
217
+ if (!isUserMenuOpen) e.currentTarget.style.backgroundColor = userRestBg;
196
218
  }}
197
219
  onClick={() => setIsUserMenuOpen(!isUserMenuOpen)}
198
220
  >
@@ -4,7 +4,7 @@ import ITTopBar from "@/components/organisms/topbar/topbar";
4
4
  import ITSidebar from "@/components/organisms/sidebar/sidebar";
5
5
  import { ITLayoutProps } from "./layout.props";
6
6
  import { theme } from "@/theme/theme";
7
- import { useITFlatAppearance } from "@/theme/theme-context";
7
+ import { useITAppearance } from "@/theme/theme-context";
8
8
 
9
9
  /**
10
10
  * Main application shell with sidebar, topbar, and content area.
@@ -49,7 +49,8 @@ export default function ITLayout({
49
49
  }: ITLayoutProps) {
50
50
  const [internalCollapsed, setInternalCollapsed] = useState(true);
51
51
  const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
52
- const flat = useITFlatAppearance();
52
+ // Bento shares the flat sidebar structure (narrow panel, no drop shadow).
53
+ const flat = useITAppearance() !== "soft";
53
54
 
54
55
  const isControlled = sidebar.isCollapsed !== undefined;
55
56
  const desktopCollapsed = isControlled ? sidebar.isCollapsed : internalCollapsed;