@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.
- package/dist/index.cjs +204 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.cts +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +200 -104
- package/dist/index.js.map +1 -1
- package/dist/layered.css +1 -1
- package/package.json +1 -1
- package/src/components/atoms/button/button.tsx +7 -0
- package/src/components/atoms/input/input.tsx +5 -0
- package/src/components/atoms/segmented-control/segmented-control.tsx +22 -5
- package/src/components/molecules/card/card.props.ts +12 -0
- package/src/components/molecules/card/card.tsx +52 -22
- package/src/components/molecules/chip-input/chip-input.tsx +2 -0
- package/src/components/molecules/multi-select/multi-select.tsx +2 -0
- package/src/components/molecules/tabs/tabs.tsx +22 -8
- 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/index.ts +4 -4
- package/src/json-renderer/catalog/axzyCatalog.ts +5 -0
- package/src/sandbox/axzyConfig.test.ts +4 -0
- package/src/sandbox/axzyConfig.ts +1 -1
- package/src/sandbox/i18n.ts +6 -0
- package/src/showcases/FeedbackShowcases.tsx +2 -1
- package/src/showcases/StructureShowcases.tsx +17 -2
- 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
|
@@ -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
|
|
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>
|
|
@@ -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 {
|
|
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
|
|
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:
|
|
43
|
-
? "
|
|
44
|
-
:
|
|
45
|
-
|
|
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:
|
|
48
|
-
? "var(--it-card-radius, var(--radius-
|
|
49
|
-
:
|
|
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
|
-
|
|
65
|
+
// Bento: borderless, a diffuse shadow at rest and a stronger one on hover.
|
|
66
|
+
boxShadow: bento
|
|
55
67
|
? onClick && isHovered
|
|
56
|
-
? "var(--shadow-
|
|
57
|
-
: "
|
|
58
|
-
:
|
|
59
|
-
? isHovered
|
|
60
|
-
? "var(--shadow-
|
|
61
|
-
: "
|
|
62
|
-
:
|
|
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(
|
|
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 ? "
|
|
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-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 {
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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:
|
|
94
|
-
|
|
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
|
|
141
|
-
{
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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 ?
|
|
211
|
+
backgroundColor: isUserMenuOpen ? userHoverBg : userRestBg,
|
|
190
212
|
}}
|
|
191
213
|
onMouseEnter={(e) => {
|
|
192
|
-
if (!isUserMenuOpen) e.currentTarget.style.backgroundColor =
|
|
214
|
+
if (!isUserMenuOpen) e.currentTarget.style.backgroundColor = userHoverBg;
|
|
193
215
|
}}
|
|
194
216
|
onMouseLeave={(e) => {
|
|
195
|
-
if (!isUserMenuOpen) e.currentTarget.style.backgroundColor =
|
|
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 {
|
|
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
|
-
|
|
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;
|