@icen.ai/ui 0.1.1 → 0.2.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 (45) hide show
  1. package/README.md +11 -0
  2. package/dist/base.css +72 -0
  3. package/dist/behaviors/accordion.mjs +34 -0
  4. package/dist/behaviors/carousel.mjs +50 -0
  5. package/dist/behaviors/charts.mjs +267 -0
  6. package/dist/behaviors/context-menu.mjs +223 -0
  7. package/dist/behaviors/dropdown.mjs +136 -0
  8. package/dist/behaviors/input.mjs +102 -0
  9. package/dist/behaviors/modal.mjs +107 -0
  10. package/dist/behaviors/nav.mjs +29 -0
  11. package/dist/behaviors/popover.mjs +164 -0
  12. package/dist/behaviors/select.mjs +132 -0
  13. package/dist/behaviors/sidebar.mjs +27 -0
  14. package/dist/behaviors/slider.mjs +72 -0
  15. package/dist/behaviors/tag-input.mjs +74 -0
  16. package/dist/behaviors/tree.mjs +44 -0
  17. package/dist/behaviors/upload.mjs +55 -0
  18. package/dist/components/breadcrumb.css +31 -0
  19. package/dist/components/card.css +56 -0
  20. package/dist/components/charts.css +95 -0
  21. package/dist/components/content.css +195 -0
  22. package/dist/components/empty.css +25 -0
  23. package/dist/components/feedback.css +106 -0
  24. package/dist/components/input.css +82 -0
  25. package/dist/components/layout.css +109 -0
  26. package/dist/components/media.css +133 -0
  27. package/dist/components/menu.css +85 -0
  28. package/dist/components/modal.css +107 -0
  29. package/dist/components/nav.css +59 -0
  30. package/dist/components/pagination.css +32 -0
  31. package/dist/components/popover.css +25 -0
  32. package/dist/components/segmented.css +112 -0
  33. package/dist/components/select.css +79 -0
  34. package/dist/components/sidebar.css +54 -0
  35. package/dist/components/slider.css +69 -0
  36. package/dist/components/steps.css +61 -0
  37. package/dist/components/switch.css +95 -0
  38. package/dist/components/tag-input.css +48 -0
  39. package/dist/components/toast.css +1 -1
  40. package/dist/components/tooltip.css +56 -0
  41. package/dist/components/upload.css +43 -0
  42. package/dist/index.mjs +1502 -1
  43. package/dist/registry.json +41 -3
  44. package/dist/ui.css +1899 -1
  45. package/package.json +1 -1
@@ -0,0 +1,106 @@
1
+ /*
2
+ * @icen.ai/ui — Component: feedback(反馈族)
3
+ * 包含:alert(四态内联横幅)/ result(终态页)/ progress(线性进度)/ spinner(加载圈)/ skeleton(占位块)
4
+ * 规格源自 opengal src/presentation/ui/feedback/toast.tsx 的 Alert 与 data/exhibit.tsx 的 Result、
5
+ * data/display.tsx 的 Progress / Spinner / Skeleton。
6
+ * base.css 未提供 spin / skeleton-pulse 关键帧,在本文件自带。
7
+ */
8
+
9
+ /* ══════════════ alert · 内联横幅 ══════════════ */
10
+ .alert {
11
+ display: flex; align-items: flex-start; gap: 12px;
12
+ padding: 12px;
13
+ border-radius: var(--radius-lg);
14
+ box-shadow: var(--token-shadow-inset);
15
+ border: 1px solid transparent;
16
+ }
17
+ .alert--info { background: var(--token-info-muted); border-color: color-mix(in srgb, var(--token-info) 15%, transparent); }
18
+ .alert--success { background: var(--token-success-muted); border-color: color-mix(in srgb, var(--token-success) 15%, transparent); }
19
+ .alert--warning { background: var(--token-warning-muted); border-color: color-mix(in srgb, var(--token-warning) 15%, transparent); }
20
+ .alert--error { background: var(--token-error-muted); border-color: color-mix(in srgb, var(--token-error) 15%, transparent); }
21
+
22
+ .alert-icon { flex-shrink: 0; margin-top: 2px; display: flex; }
23
+ .alert-icon svg { width: 16px; height: 16px; }
24
+ .alert--info .alert-icon, .alert--info .alert-title { color: var(--token-info); }
25
+ .alert--success .alert-icon, .alert--success .alert-title { color: var(--token-success); }
26
+ .alert--warning .alert-icon, .alert--warning .alert-title { color: var(--token-warning); }
27
+ .alert--error .alert-icon, .alert--error .alert-title { color: var(--token-error); }
28
+
29
+ .alert-content { flex: 1; min-width: 0; }
30
+ .alert-title { font-size: 13px; font-weight: 500; margin: 0; }
31
+ .alert-body { font-size: 12px; color: var(--token-text-muted); line-height: 1.6; }
32
+ .alert-close {
33
+ margin-left: auto; flex-shrink: 0;
34
+ display: flex; align-items: center; justify-content: center;
35
+ padding: 2px; border: 0; border-radius: var(--radius-xs);
36
+ background: none; color: var(--token-text-faint); cursor: pointer;
37
+ }
38
+ .alert-close:hover { color: var(--token-text); background: color-mix(in srgb, var(--token-text) 6%, transparent); }
39
+ .alert-close svg { width: 14px; height: 14px; }
40
+
41
+ /* ══════════════ result · 终态页 ══════════════ */
42
+ .result {
43
+ display: flex; flex-direction: column; align-items: center; justify-content: center;
44
+ text-align: center;
45
+ padding: 40px 24px;
46
+ }
47
+ .result-icon {
48
+ width: 64px; height: 64px;
49
+ border-radius: var(--radius-full);
50
+ display: flex; align-items: center; justify-content: center;
51
+ box-shadow: var(--token-shadow-inset);
52
+ border: 1px solid transparent;
53
+ margin-bottom: 16px;
54
+ }
55
+ .result-icon svg { width: 28px; height: 28px; }
56
+ .result--success .result-icon { background: var(--token-success-muted); color: var(--token-success); border-color: color-mix(in srgb, var(--token-success) 10%, transparent); }
57
+ .result--error .result-icon { background: var(--token-error-muted); color: var(--token-error); border-color: color-mix(in srgb, var(--token-error) 10%, transparent); }
58
+ .result--warning .result-icon { background: var(--token-warning-muted); color: var(--token-warning); border-color: color-mix(in srgb, var(--token-warning) 10%, transparent); }
59
+ .result--info .result-icon { background: var(--token-info-muted); color: var(--token-info); border-color: color-mix(in srgb, var(--token-info) 10%, transparent); }
60
+ .result-title { font-size: 17px; font-weight: 600; color: var(--token-text); margin: 0; }
61
+ .result-desc { font-size: 13px; color: var(--token-text-muted); margin: 4px 0 0; max-width: 360px; }
62
+ .result-extra { display: flex; align-items: center; gap: 8px; margin-top: 20px; }
63
+
64
+ /* ══════════════ progress · 线性进度 ══════════════ */
65
+ .progress { display: flex; align-items: center; gap: 8px; }
66
+ .progress-track {
67
+ flex: 1; height: 6px; overflow: hidden;
68
+ border-radius: var(--radius-full);
69
+ background: color-mix(in srgb, var(--token-line-soft) 40%, transparent);
70
+ }
71
+ .progress-bar {
72
+ height: 100%;
73
+ border-radius: var(--radius-full);
74
+ background: var(--token-accent);
75
+ transition: width 500ms var(--ease-out);
76
+ }
77
+ .progress--sm .progress-track { height: 4px; }
78
+ .progress-label { font-size: 11px; color: var(--token-text-muted); font-variant-numeric: tabular-nums; }
79
+
80
+ /* ══════════════ spinner · 加载圈 ══════════════ */
81
+ .spinner {
82
+ width: 20px; height: 20px;
83
+ border: 2px solid var(--token-accent);
84
+ border-top-color: transparent;
85
+ border-radius: var(--radius-full);
86
+ animation: icen-spin 1s linear infinite;
87
+ }
88
+ .spinner--sm { width: 16px; height: 16px; }
89
+ .spinner--lg { width: 32px; height: 32px; }
90
+
91
+ @keyframes icen-spin {
92
+ to { transform: rotate(360deg); }
93
+ }
94
+
95
+ /* ══════════════ skeleton · 占位块 ══════════════ */
96
+ .skeleton {
97
+ background: var(--token-control-hover);
98
+ border-radius: var(--radius-sm);
99
+ animation: icen-skeleton-pulse 2s ease-in-out infinite;
100
+ }
101
+ .skeleton--circle { border-radius: var(--radius-full); }
102
+
103
+ @keyframes icen-skeleton-pulse {
104
+ 0%, 100% { opacity: 1; }
105
+ 50% { opacity: 0.5; }
106
+ }
@@ -0,0 +1,82 @@
1
+ /*
2
+ * @icen.ai/ui — Component: input(wrapper 式输入框 / 多行 / 搜索)
3
+ * 规格源自 opengal primitives/input.tsx;与 behaviors/input.ts 配套。
4
+ *
5
+ * 契约:
6
+ * <div class="input-wrap control [--sm|--lg] [.is-error] [.is-disabled]">
7
+ * <span class="input-leading">…</span> 可选前缀
8
+ * <input class="input" />
9
+ * <button class="input-clear">…</button> 可选清除钮(behavior 清空并聚焦)
10
+ * <button class="input-pw-toggle">…</button> 可选密码可见切换(behavior 驱动)
11
+ * <span class="input-trailing">…</span> 可选后缀
12
+ * </div>
13
+ * 多行:<div class="textarea-wrap control"><textarea class="textarea" data-autosize></textarea></div>
14
+ * 搜索:.input-wrap--search = leading 放大镜 + .input-clear 的结构约定(无额外视觉)
15
+ */
16
+
17
+ .input-wrap {
18
+ display: flex; align-items: center; gap: 8px; width: 100%;
19
+ height: 36px; padding: 0 12px; font-size: 13px;
20
+ background: var(--token-control);
21
+ border: 1px solid var(--token-outline-soft);
22
+ border-radius: var(--radius-lg);
23
+ box-shadow: var(--token-shadow-inset);
24
+ }
25
+ .input-wrap:focus-within { border-color: var(--token-accent); }
26
+ .input-wrap.is-error, .input-wrap.is-error:focus-within { border-color: var(--token-error); }
27
+ .input-wrap.is-disabled { opacity: 0.4; }
28
+ .input-wrap.is-disabled .input { cursor: not-allowed; }
29
+
30
+ /* 尺寸三档:高度 / 横向内边距 / 字号 */
31
+ .input-wrap--sm { height: 32px; padding: 0 10px; font-size: 12px; }
32
+ .input-wrap--lg { height: 40px; padding: 0 14px; font-size: 14px; }
33
+
34
+ .input {
35
+ flex: 1; min-width: 0; padding: 0;
36
+ background: transparent; border: none; outline: none;
37
+ color: var(--token-text); font: inherit;
38
+ }
39
+ .input::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
40
+
41
+ .input-leading, .input-trailing {
42
+ display: inline-flex; align-items: center; flex-shrink: 0;
43
+ color: var(--token-text-faint);
44
+ }
45
+
46
+ .input-clear {
47
+ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
48
+ padding: 2px; border: none; background: transparent; cursor: pointer;
49
+ border-radius: var(--radius-full); color: var(--token-text-faint);
50
+ transition: color var(--duration-normal) var(--ease-out),
51
+ background-color var(--duration-normal) var(--ease-out);
52
+ }
53
+ .input-clear:hover { color: var(--token-text); background: var(--token-surface-hover); }
54
+
55
+ .input-pw-toggle {
56
+ display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
57
+ padding: 2px; border: none; background: transparent; cursor: pointer;
58
+ color: var(--token-text-faint);
59
+ transition: color var(--duration-normal) var(--ease-out);
60
+ }
61
+ .input-pw-toggle:hover { color: var(--token-text); }
62
+
63
+ /* ── 多行:同一 wrapper 视觉,内部 textarea 裸奔 ── */
64
+ .textarea-wrap {
65
+ display: flex; width: 100%;
66
+ background: var(--token-control);
67
+ border: 1px solid var(--token-outline-soft);
68
+ border-radius: var(--radius-lg);
69
+ box-shadow: var(--token-shadow-inset);
70
+ }
71
+ .textarea-wrap:focus-within { border-color: var(--token-accent); }
72
+ .textarea-wrap.is-error, .textarea-wrap.is-error:focus-within { border-color: var(--token-error); }
73
+ .textarea-wrap.is-disabled { opacity: 0.4; }
74
+ .textarea-wrap.is-disabled .textarea { cursor: not-allowed; }
75
+
76
+ .textarea {
77
+ width: 100%; padding: 12px;
78
+ background: transparent; border: none; outline: none; resize: none;
79
+ color: var(--token-text); font: inherit; font-size: 13px; line-height: 1.5;
80
+ min-height: calc(3 * 1.5em + 24px); /* 3 行 + 上下 padding */
81
+ }
82
+ .textarea::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
@@ -0,0 +1,109 @@
1
+ /*
2
+ * @icen.ai/ui — Component: layout(布局原语,全部是工具类,无 JS)
3
+ * stack / divider / surface / text / grid / center / container / aspect / visually-hidden
4
+ * 全部消费 --token-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
5
+ */
6
+
7
+ /* ── Stack:flex 堆叠(默认列向,gap 8px) ── */
8
+ .stack { display: flex; flex-direction: column; gap: 8px; }
9
+ .stack--row { flex-direction: row; }
10
+ .stack--inline { display: inline-flex; }
11
+ .stack--wrap { flex-wrap: wrap; }
12
+ .stack--gap-0 { gap: 0; }
13
+ .stack--gap-1 { gap: 4px; }
14
+ .stack--gap-2 { gap: 8px; }
15
+ .stack--gap-3 { gap: 12px; }
16
+ .stack--gap-4 { gap: 16px; }
17
+ .stack--gap-6 { gap: 24px; }
18
+ .stack--gap-8 { gap: 32px; }
19
+ .stack--gap-12 { gap: 48px; }
20
+ .stack--gap-16 { gap: 64px; }
21
+ .stack--items-start { align-items: flex-start; }
22
+ .stack--items-center { align-items: center; }
23
+ .stack--items-end { align-items: flex-end; }
24
+ .stack--items-stretch { align-items: stretch; }
25
+ .stack--items-baseline { align-items: baseline; }
26
+ .stack--justify-start { justify-content: flex-start; }
27
+ .stack--justify-center { justify-content: center; }
28
+ .stack--justify-end { justify-content: flex-end; }
29
+ .stack--justify-between { justify-content: space-between; }
30
+ .stack--justify-around { justify-content: space-around; }
31
+ .stack--justify-evenly { justify-content: space-evenly; }
32
+
33
+ /* ── Divider:分隔线 ── */
34
+ .divider { background: var(--token-outline-soft); height: 1px; width: 100%; }
35
+ .divider--vertical { width: 1px; height: 100%; align-self: stretch; }
36
+
37
+ /* ── Surface:卡片容器(默认 inset 阴影 + 16px padding) ── */
38
+ .surface {
39
+ border-radius: var(--radius-lg);
40
+ background: var(--token-card);
41
+ border: 1px solid var(--token-outline-soft);
42
+ box-shadow: var(--token-shadow-inset);
43
+ padding: 16px;
44
+ }
45
+ .surface--elevated { box-shadow: var(--token-shadow-card); }
46
+ .surface--overlay {
47
+ background: var(--token-card-strong);
48
+ border-color: var(--token-outline);
49
+ box-shadow: var(--token-shadow-overlay);
50
+ }
51
+ .surface--p-none { padding: 0; }
52
+ .surface--p-sm { padding: 12px; }
53
+ .surface--p-md { padding: 16px; }
54
+ .surface--p-lg { padding: 20px; }
55
+
56
+ /* ── Text:字号 / 语义色 / 截断 ── */
57
+ .text--xs { font-size: 11px; }
58
+ .text--sm { font-size: 12px; }
59
+ .text--base { font-size: 13px; }
60
+ .text--md { font-size: 14px; }
61
+ .text--lg { font-size: 15px; }
62
+ .text--xl { font-size: 18px; }
63
+ .text--2xl { font-size: 22px; }
64
+ .text--muted { color: var(--token-text-muted); }
65
+ .text--faint { color: var(--token-text-faint); }
66
+ .text--accent { color: var(--token-accent); }
67
+ .text--success { color: var(--token-success); }
68
+ .text--warning { color: var(--token-warning); }
69
+ .text--error { color: var(--token-error); }
70
+ .text--truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
71
+
72
+ /* ── Grid:栅格(默认 gap 12px) ── */
73
+ .grid { display: grid; gap: 12px; }
74
+ .grid--cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
75
+ .grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
76
+ .grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
77
+ .grid--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
78
+ .grid--cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
79
+ .grid--cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
80
+ .grid--gap-0 { gap: 0; }
81
+ .grid--gap-4 { gap: 4px; }
82
+ .grid--gap-8 { gap: 8px; }
83
+ .grid--gap-12 { gap: 12px; }
84
+ .grid--gap-16 { gap: 16px; }
85
+ .grid--gap-24 { gap: 24px; }
86
+
87
+ /* ── Center:flex 居中盒 ── */
88
+ .center { display: flex; align-items: center; justify-content: center; }
89
+
90
+ /* ── Container:限宽容器(默认 960px) ── */
91
+ .container { width: 100%; margin-inline: auto; padding-inline: 16px; max-width: 960px; }
92
+ .container--sm { max-width: 480px; }
93
+ .container--md { max-width: 720px; }
94
+ .container--lg { max-width: 960px; }
95
+ .container--xl { max-width: 1200px; }
96
+ .container--full { max-width: none; }
97
+
98
+ /* ── Aspect:固定比例盒(padding-bottom hack,内容 absolute inset:0) ── */
99
+ .aspect { position: relative; width: 100%; overflow: hidden; padding-bottom: 56.25%; }
100
+ .aspect--square { padding-bottom: 100%; }
101
+ .aspect--4-3 { padding-bottom: 75%; }
102
+ .aspect--2-3 { padding-bottom: 150%; }
103
+ .aspect > * { position: absolute; inset: 0; }
104
+
105
+ /* ── VisuallyHidden:屏幕阅读器专用文本(sr-only 等价) ── */
106
+ .visually-hidden {
107
+ position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
108
+ overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
109
+ }
@@ -0,0 +1,133 @@
1
+ /*
2
+ * @icen.ai/ui — Component: media(媒体族)
3
+ * 包含:avatar(头像)/ media-card(封面卡,滑出 overlay)/ rating(星级评分)/ carousel(轮播,与 behaviors/carousel 配套)
4
+ * carousel 归入本文件(无独立 carousel.css 配额);规格源自 opengal data/display.tsx 的 Avatar、
5
+ * data/cartridge-card.tsx 的 hover overlay、data/exhibit.tsx 的 Carousel 与 Rating。
6
+ */
7
+
8
+ /* ══════════════ avatar · 头像 ══════════════ */
9
+ .avatar {
10
+ position: relative;
11
+ display: inline-flex; align-items: center; justify-content: center;
12
+ overflow: hidden;
13
+ border-radius: var(--radius-full);
14
+ background: var(--token-accent-subtle);
15
+ color: var(--token-accent);
16
+ font-weight: 600;
17
+ width: 36px; height: 36px; font-size: 12px;
18
+ flex-shrink: 0;
19
+ }
20
+ .avatar--xs { width: 20px; height: 20px; font-size: 9px; }
21
+ .avatar--sm { width: 28px; height: 28px; font-size: 10px; }
22
+ .avatar--lg { width: 48px; height: 48px; font-size: 14px; }
23
+ .avatar img { width: 100%; height: 100%; object-fit: cover; }
24
+
25
+ /* ══════════════ media-card · 封面卡 ══════════════ */
26
+ .media-card {
27
+ position: relative;
28
+ overflow: hidden;
29
+ border-radius: var(--radius-lg);
30
+ transition: transform 300ms var(--ease-out), box-shadow 300ms var(--ease-out);
31
+ }
32
+ .media-card:hover {
33
+ transform: translateY(-6px);
34
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2), 0 0 20px var(--token-accent-glow);
35
+ }
36
+ .media-card-cover {
37
+ display: block;
38
+ width: 100%;
39
+ aspect-ratio: 2 / 3;
40
+ object-fit: cover;
41
+ }
42
+ .media-card-overlay {
43
+ position: absolute; left: 0; right: 0; bottom: 0;
44
+ padding: 32px 12px 10px;
45
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.35) 65%, transparent);
46
+ backdrop-filter: blur(10px);
47
+ -webkit-backdrop-filter: blur(10px);
48
+ color: #fff;
49
+ transform: translateY(100%);
50
+ transition: transform 200ms var(--ease-out);
51
+ pointer-events: none;
52
+ }
53
+ .media-card:hover .media-card-overlay {
54
+ transform: translateY(0);
55
+ transition-delay: 80ms;
56
+ }
57
+
58
+ /* ══════════════ rating · 星级评分 ══════════════ */
59
+ .rating { display: inline-flex; align-items: center; gap: 2px; }
60
+ .rating-star {
61
+ padding: 2px;
62
+ border-radius: var(--radius-xs);
63
+ background: none; border: 0;
64
+ cursor: pointer;
65
+ display: inline-flex; align-items: center; justify-content: center;
66
+ }
67
+ .rating-star svg {
68
+ width: 18px; height: 18px; display: block;
69
+ fill: transparent;
70
+ stroke: var(--token-line-strong);
71
+ stroke-width: 1.6;
72
+ transition: fill var(--duration-normal) var(--ease-out), stroke var(--duration-normal) var(--ease-out);
73
+ }
74
+ .rating--sm .rating-star svg { width: 14px; height: 14px; }
75
+ .rating--lg .rating-star svg { width: 22px; height: 22px; }
76
+ .rating-star.is-filled svg { fill: var(--token-warning); stroke: var(--token-warning); }
77
+ .rating-star.is-half svg {
78
+ fill: color-mix(in srgb, var(--token-warning) 50%, transparent);
79
+ stroke: var(--token-warning);
80
+ }
81
+
82
+ /* ══════════════ carousel · 轮播(behaviors/carousel 配套) ══════════════
83
+ * DOM 契约:
84
+ * <div class="carousel">
85
+ * <div class="carousel-track"><div class="carousel-slide">…</div>×n</div>
86
+ * <button class="carousel-arrow carousel-arrow--prev">…</button>
87
+ * <button class="carousel-arrow carousel-arrow--next">…</button>
88
+ * <div class="carousel-dots"></div> <!-- dots 由 behavior 自动生成 -->
89
+ * </div>
90
+ */
91
+ .carousel {
92
+ position: relative;
93
+ overflow: hidden;
94
+ border-radius: var(--radius-lg);
95
+ background: var(--token-control);
96
+ }
97
+ .carousel-track {
98
+ display: flex;
99
+ transition: transform 300ms var(--ease-out);
100
+ }
101
+ .carousel-slide { width: 100%; flex-shrink: 0; }
102
+ .carousel-arrow {
103
+ position: absolute; top: 50%; transform: translateY(-50%);
104
+ width: 32px; height: 32px;
105
+ border-radius: var(--radius-full);
106
+ background: color-mix(in srgb, var(--token-card) 78%, transparent);
107
+ border: 1px solid var(--token-outline-soft);
108
+ color: var(--token-text);
109
+ display: flex; align-items: center; justify-content: center;
110
+ backdrop-filter: blur(4px);
111
+ -webkit-backdrop-filter: blur(4px);
112
+ box-shadow: var(--token-shadow-card);
113
+ cursor: pointer;
114
+ transition: background-color var(--duration-normal) var(--ease-out), transform var(--duration-normal) var(--ease-out);
115
+ }
116
+ .carousel-arrow:hover { background: var(--token-card-strong); }
117
+ .carousel-arrow:active { transform: translateY(-50%) scale(0.95); }
118
+ .carousel-arrow svg { width: 16px; height: 16px; }
119
+ .carousel-arrow--prev { left: 8px; }
120
+ .carousel-arrow--next { right: 8px; }
121
+ .carousel-dots {
122
+ position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
123
+ display: flex; gap: 6px;
124
+ }
125
+ .carousel-dot {
126
+ width: 6px; height: 6px; padding: 0;
127
+ border: 0; border-radius: var(--radius-full);
128
+ background: color-mix(in srgb, var(--token-card) 70%, transparent);
129
+ cursor: pointer;
130
+ transition: width var(--duration-normal) var(--ease-out), background-color var(--duration-normal) var(--ease-out);
131
+ }
132
+ .carousel-dot:hover { background: color-mix(in srgb, var(--token-accent) 70%, transparent); }
133
+ .carousel-dot.active { width: 20px; background: var(--token-accent); }
@@ -0,0 +1,85 @@
1
+ /*
2
+ * @icen.ai/ui — Component: menu(菜单面板,与 behaviors/dropdown、context-menu 配套)
3
+ * 面板由 JS portal 到 document.body 并按 fixed 定位;data-side 决定进场位移方向。
4
+ * 规格源自 opengal overlay/dropdown + overlay/context-menu。
5
+ */
6
+
7
+ .menu {
8
+ position: fixed; z-index: var(--z-popover);
9
+ min-width: 180px; max-height: min(70vh, 420px); overflow-y: auto;
10
+ margin: 0; padding: 4px;
11
+ background: color-mix(in srgb, var(--token-card-strong) 92%, transparent);
12
+ backdrop-filter: blur(24px);
13
+ border: 1px solid var(--token-outline);
14
+ box-shadow: var(--token-shadow-overlay);
15
+ border-radius: var(--radius-lg);
16
+ animation: icen-menu-in 150ms var(--ease-out);
17
+ }
18
+ .menu[data-side='top'] { animation-name: icen-menu-in-top; }
19
+
20
+ /* 右键菜单修正(opengal context-menu:min 218 / max 286 / z 9999 / 入场 100ms) */
21
+ .menu--context {
22
+ min-width: 218px; max-width: 286px; z-index: 9999;
23
+ animation-duration: 100ms;
24
+ }
25
+
26
+ /* ── 分组标签 / 头(context-menu 的 header 项) ── */
27
+ .menu-label {
28
+ padding: 4px 8px; font-size: 10px; font-weight: 600;
29
+ text-transform: uppercase; letter-spacing: 0.08em;
30
+ color: var(--token-text-faint);
31
+ }
32
+ .menu-header { padding: 8px 10px 6px; }
33
+ .menu-header-label {
34
+ display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
35
+ font-size: 11px; font-weight: 500; line-height: 16px; color: var(--token-text);
36
+ }
37
+ .menu-header-desc {
38
+ display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
39
+ font-size: 10px; line-height: 14px; color: var(--token-text-faint);
40
+ }
41
+
42
+ /* ── 菜单项 ── */
43
+ .menu-item {
44
+ position: relative; display: flex; align-items: center; gap: 8px;
45
+ width: 100%; padding: 6px 8px;
46
+ background: none; border: 0; font: inherit; font-size: 12px; text-align: left;
47
+ color: var(--token-text);
48
+ border-radius: var(--radius-md); cursor: pointer; user-select: none;
49
+ }
50
+ .menu-item.is-highlighted, .menu-item:focus {
51
+ background: var(--token-surface-hover); color: var(--token-accent); outline: none;
52
+ }
53
+ .menu-item--danger { color: var(--token-error); }
54
+ .menu-item--danger.is-highlighted, .menu-item--danger:focus {
55
+ background: var(--token-error-muted); color: var(--token-error);
56
+ }
57
+ .menu-item.is-disabled, .menu-item:disabled { opacity: 0.5; pointer-events: none; }
58
+
59
+ .menu-item-icon {
60
+ display: inline-flex; align-items: center; justify-content: center;
61
+ width: 14px; height: 14px; flex-shrink: 0;
62
+ }
63
+ .menu-item-icon:empty { width: 16px; height: auto; } /* 无图标时 16px 宽占位 */
64
+ .menu-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
65
+ .menu-item-sub { display: block; font-size: 10px; line-height: 1.2; color: var(--token-text-faint); }
66
+ .menu-item-shortcut { font-size: 10px; color: var(--token-text-faint); }
67
+
68
+ /* 勾选项:左勾 ✓ + 缩进 */
69
+ .menu-item-check {
70
+ position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
71
+ color: var(--token-accent); font-size: 12px; line-height: 1;
72
+ }
73
+ .menu-item--checked { padding-left: 28px; }
74
+
75
+ .menu-separator { margin: 6px 8px; height: 1px; background: var(--token-line-soft); }
76
+
77
+ /* ── 进场:fade + scale(.95→1),bottom 从上方 8px 滑入、top 从下方 8px 滑入 ── */
78
+ @keyframes icen-menu-in {
79
+ from { opacity: 0; transform: scale(0.95) translateY(-8px); }
80
+ to { opacity: 1; transform: scale(1) translateY(0); }
81
+ }
82
+ @keyframes icen-menu-in-top {
83
+ from { opacity: 0; transform: scale(0.95) translateY(8px); }
84
+ to { opacity: 1; transform: scale(1) translateY(0); }
85
+ }
@@ -0,0 +1,107 @@
1
+ /*
2
+ * @icen.ai/ui — Component: modal(对话框 + 贴边 Sheet,与 behaviors/modal 配套)
3
+ * 契约:<div class="modal-backdrop" data-modal="id" hidden> > .modal[role=dialog]
4
+ * 规格源自 opengal overlay/dialog:backdrop fadeIn 150ms、面板 dialogScaleIn
5
+ * 200ms cubic-bezier(.16,1,.3,1)、Sheet 四向 slideIn 200ms 位移 40px。
6
+ */
7
+
8
+ .modal-backdrop {
9
+ position: fixed; inset: 0; z-index: var(--z-dialog);
10
+ display: flex; align-items: center; justify-content: center; padding: 16px;
11
+ background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(4px);
12
+ animation: icen-modal-fade-in 150ms ease-out;
13
+ }
14
+ .modal-backdrop[hidden] { display: none; }
15
+
16
+ .modal {
17
+ display: flex; flex-direction: column; overflow: hidden;
18
+ width: 100%; max-width: 480px; max-height: calc(100vh - 32px);
19
+ background: var(--token-card-strong); color: var(--token-text);
20
+ border: 1px solid var(--token-outline);
21
+ box-shadow: var(--token-shadow-overlay);
22
+ border-radius: var(--radius-xl);
23
+ animation: icen-modal-scale-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
24
+ }
25
+ .modal:focus { outline: none; }
26
+
27
+ /* ── 尺寸档 ── */
28
+ .modal--sm { max-width: 360px; }
29
+ .modal--lg { max-width: 640px; }
30
+ .modal--xl { max-width: 800px; }
31
+ .modal--full { width: calc(100vw - 32px); max-width: none; height: calc(100vh - 32px); max-height: none; }
32
+
33
+ /* ── 头部 / 主体 / 底栏 ── */
34
+ .modal-header {
35
+ display: flex; align-items: flex-start; justify-content: space-between;
36
+ gap: 16px; padding: 20px 24px 12px;
37
+ }
38
+ .modal-title { margin: 0; font-size: 15px; font-weight: 600; }
39
+ .modal-description { margin: 2px 0 0; font-size: 12px; color: var(--token-text-muted); }
40
+ .modal-close {
41
+ display: inline-flex; align-items: center; justify-content: center;
42
+ width: 28px; height: 28px; padding: 0; flex-shrink: 0;
43
+ background: none; border: 0; cursor: pointer;
44
+ color: var(--token-text-faint); font-size: 16px; line-height: 1;
45
+ border-radius: var(--radius-md);
46
+ }
47
+ .modal-close:hover { background: var(--token-surface-hover); color: var(--token-text); }
48
+
49
+ .modal-body { flex: 1; overflow-y: auto; padding: 0 24px 20px; }
50
+ .modal-footer {
51
+ display: flex; justify-content: flex-end; gap: 8px; padding: 16px 24px;
52
+ border-top: 1px solid color-mix(in srgb, var(--token-line-soft) 40%, transparent);
53
+ }
54
+
55
+ /* ── Sheet 变体:面板 fixed 贴边,radius 只保留非贴边侧 ── */
56
+ .modal--sheet-left, .modal--sheet-right, .modal--sheet-top, .modal--sheet-bottom {
57
+ position: fixed; max-width: none; max-height: none; border-radius: 0;
58
+ }
59
+ .modal--sheet-left {
60
+ left: 0; top: 0; bottom: 0; width: 360px; height: 100%;
61
+ border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
62
+ animation-name: icen-modal-slide-left;
63
+ }
64
+ .modal--sheet-right {
65
+ right: 0; top: 0; bottom: 0; width: 360px; height: 100%;
66
+ border-radius: var(--radius-xl) 0 0 var(--radius-xl);
67
+ animation-name: icen-modal-slide-right;
68
+ }
69
+ .modal--sheet-top {
70
+ top: 0; left: 0; right: 0; width: 100%; height: 300px;
71
+ border-radius: 0 0 var(--radius-xl) var(--radius-xl);
72
+ animation-name: icen-modal-slide-top;
73
+ }
74
+ .modal--sheet-bottom {
75
+ bottom: 0; left: 0; right: 0; width: 100%; height: 300px;
76
+ border-radius: var(--radius-xl) var(--radius-xl) 0 0;
77
+ animation-name: icen-modal-slide-bottom;
78
+ }
79
+ .modal--sheet-left.modal--sheet-sm, .modal--sheet-right.modal--sheet-sm { width: 280px; }
80
+ .modal--sheet-left.modal--sheet-lg, .modal--sheet-right.modal--sheet-lg { width: 480px; }
81
+ .modal--sheet-top.modal--sheet-lg, .modal--sheet-bottom.modal--sheet-lg { height: 50vh; }
82
+
83
+ /* ── 进场动画(与 opengal src/index.css 同参数) ── */
84
+ @keyframes icen-modal-fade-in {
85
+ from { opacity: 0; }
86
+ to { opacity: 1; }
87
+ }
88
+ @keyframes icen-modal-scale-in {
89
+ from { opacity: 0; transform: scale(0.96) translateY(8px); }
90
+ to { opacity: 1; transform: scale(1) translateY(0); }
91
+ }
92
+ @keyframes icen-modal-slide-left {
93
+ from { opacity: 0; transform: translateX(-40px); }
94
+ to { opacity: 1; transform: translateX(0); }
95
+ }
96
+ @keyframes icen-modal-slide-right {
97
+ from { opacity: 0; transform: translateX(40px); }
98
+ to { opacity: 1; transform: translateX(0); }
99
+ }
100
+ @keyframes icen-modal-slide-top {
101
+ from { opacity: 0; transform: translateY(-40px); }
102
+ to { opacity: 1; transform: translateY(0); }
103
+ }
104
+ @keyframes icen-modal-slide-bottom {
105
+ from { opacity: 0; transform: translateY(40px); }
106
+ to { opacity: 1; transform: translateY(0); }
107
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * @icen.ai/ui — Component: nav(玻璃顶栏,scroll 收缩配套 behaviors/nav)
3
+ * 用法:<nav class="nav nav--sticky" data-scroll-reactive>
4
+ * <div class="nav-left">…</div><div class="nav-center">…</div><div class="nav-right">…</div>
5
+ * </nav>
6
+ * 三段:.nav-left / .nav-center(flex:1 居中)/ .nav-right,整行补充用 .nav-bottom。
7
+ * 按钮:.nav-btn(HTML 组合 .motion .pressable 基元),.is-active / .is-disabled / .nav-btn--icon。
8
+ * 契约:sticky/fixed 挂 --z-chrome 层;data-scroll-reactive 时 behaviors/nav 依 scrollY>12 切
9
+ * .is-scrolled——未滚动更透且无边线,滚动后底色更实 + blur 16px。
10
+ */
11
+
12
+ .nav {
13
+ display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
14
+ height: 56px; padding: 0 16px;
15
+ background: var(--token-surface-glass);
16
+ backdrop-filter: blur(12px) saturate(1.1);
17
+ -webkit-backdrop-filter: blur(12px) saturate(1.1);
18
+ border-bottom: 1px solid var(--token-outline-soft);
19
+ transition: all var(--duration-slow) var(--ease-out);
20
+ }
21
+ /* 带整行 bottom 时放开固定高度,第一行仍保持 56px */
22
+ .nav:has(.nav-bottom) { height: auto; min-height: 56px; }
23
+ .nav--sticky { position: sticky; top: 0; z-index: var(--z-chrome); }
24
+ .nav--fixed { position: fixed; top: 0; left: 0; right: 0; z-index: var(--z-chrome); }
25
+
26
+ /* scroll-reactive:未滚动更透、底边线透明(滚动后由 .is-scrolled 接管) */
27
+ .nav[data-scroll-reactive]:not(.is-scrolled) {
28
+ background: color-mix(in srgb, var(--token-surface-glass) 70%, transparent);
29
+ backdrop-filter: blur(8px);
30
+ -webkit-backdrop-filter: blur(8px);
31
+ border-bottom-color: transparent;
32
+ }
33
+ .nav.is-scrolled {
34
+ background: color-mix(in srgb, var(--token-surface-glass) 92%, transparent);
35
+ backdrop-filter: blur(16px) saturate(1.2);
36
+ -webkit-backdrop-filter: blur(16px) saturate(1.2);
37
+ }
38
+
39
+ .nav-left, .nav-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
40
+ .nav-center { flex: 1; display: flex; align-items: center; justify-content: center; gap: 4px; }
41
+ .nav-bottom { width: 100%; }
42
+
43
+ .nav-btn {
44
+ display: inline-flex; align-items: center; gap: 6px;
45
+ border-radius: var(--radius-full); padding: 6px 12px;
46
+ font-size: 13px; color: var(--token-text-muted);
47
+ background: none; border: 0; cursor: pointer;
48
+ }
49
+ .nav-btn > svg { width: 14px; height: 14px; flex-shrink: 0; }
50
+ .nav-btn:not(.is-active):hover { color: var(--token-text); background: var(--token-control-hover); }
51
+ .nav-btn.is-active {
52
+ background: var(--token-accent); color: var(--token-on-accent);
53
+ font-weight: 500; box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
54
+ }
55
+ .nav-btn.is-disabled, .nav-btn:disabled { opacity: .4; pointer-events: none; }
56
+ .nav-btn--icon { padding: 6px 10px; }
57
+ .nav-btn--icon > svg { width: 15px; height: 15px; }
58
+
59
+ .nav-divider { width: 1px; height: 20px; margin: 0 2px; background: var(--token-outline-soft); }