@icen.ai/ui 0.1.0 → 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.
- package/README.md +11 -0
- package/dist/base.css +72 -0
- package/dist/behaviors/accordion.mjs +34 -0
- package/dist/behaviors/carousel.mjs +50 -0
- package/dist/behaviors/charts.mjs +267 -0
- package/dist/behaviors/context-menu.mjs +223 -0
- package/dist/behaviors/dropdown.mjs +136 -0
- package/dist/behaviors/input.mjs +102 -0
- package/dist/behaviors/modal.mjs +107 -0
- package/dist/behaviors/nav.mjs +29 -0
- package/dist/behaviors/popover.mjs +164 -0
- package/dist/behaviors/select.mjs +132 -0
- package/dist/behaviors/sidebar.mjs +27 -0
- package/dist/behaviors/slider.mjs +72 -0
- package/dist/behaviors/tag-input.mjs +74 -0
- package/dist/behaviors/tree.mjs +44 -0
- package/dist/behaviors/upload.mjs +55 -0
- package/dist/components/breadcrumb.css +31 -0
- package/dist/components/card.css +56 -0
- package/dist/components/charts.css +95 -0
- package/dist/components/content.css +195 -0
- package/dist/components/empty.css +25 -0
- package/dist/components/feedback.css +106 -0
- package/dist/components/input.css +82 -0
- package/dist/components/layout.css +109 -0
- package/dist/components/media.css +133 -0
- package/dist/components/menu.css +85 -0
- package/dist/components/modal.css +107 -0
- package/dist/components/nav.css +59 -0
- package/dist/components/pagination.css +32 -0
- package/dist/components/popover.css +25 -0
- package/dist/components/segmented.css +112 -0
- package/dist/components/select.css +79 -0
- package/dist/components/sidebar.css +54 -0
- package/dist/components/slider.css +69 -0
- package/dist/components/steps.css +61 -0
- package/dist/components/switch.css +95 -0
- package/dist/components/tag-input.css +48 -0
- package/dist/components/toast.css +1 -1
- package/dist/components/tooltip.css +56 -0
- package/dist/components/upload.css +43 -0
- package/dist/index.mjs +1502 -1
- package/dist/registry.json +41 -3
- package/dist/ui.css +1899 -1
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: pagination(分页)
|
|
3
|
+
* 用法:<nav class="pagination" aria-label="分页">
|
|
4
|
+
* <button class="page-btn motion is-disabled">‹</button>
|
|
5
|
+
* <button class="page-btn motion">1</button>
|
|
6
|
+
* <span class="page-ellipsis">…</span>
|
|
7
|
+
* <button class="page-btn motion is-active" aria-current="page">5</button>
|
|
8
|
+
* <button class="page-btn motion">›</button>
|
|
9
|
+
* </nav>
|
|
10
|
+
* 翻页逻辑由消费方实现,这里只提供静态样式契约。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.pagination { display: flex; align-items: center; gap: 4px; }
|
|
14
|
+
|
|
15
|
+
.page-btn {
|
|
16
|
+
min-width: 28px; height: 28px; padding: 0 6px;
|
|
17
|
+
border-radius: var(--radius-md); font-size: 12px;
|
|
18
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
19
|
+
color: var(--token-text-muted); background: none; border: 0; cursor: pointer;
|
|
20
|
+
}
|
|
21
|
+
.page-btn:not(.is-active):hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
22
|
+
.page-btn.is-active {
|
|
23
|
+
background: var(--token-accent); color: var(--token-on-accent);
|
|
24
|
+
font-weight: 500; box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
|
25
|
+
}
|
|
26
|
+
.page-btn.is-disabled, .page-btn:disabled { opacity: .3; pointer-events: none; }
|
|
27
|
+
|
|
28
|
+
.page-ellipsis {
|
|
29
|
+
min-width: 28px; height: 28px; font-size: 12px;
|
|
30
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
31
|
+
color: var(--token-text-faint);
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: popover(锚定浮层,与 behaviors/popover 配套)
|
|
3
|
+
* 面板由 JS portal 到 document.body 并按 fixed 定位;data-side 由布局函数写入。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.popover {
|
|
7
|
+
position: fixed; z-index: var(--z-popover);
|
|
8
|
+
padding: 8px;
|
|
9
|
+
background: var(--token-card-strong); color: var(--token-text);
|
|
10
|
+
border: 1px solid var(--token-outline);
|
|
11
|
+
box-shadow: var(--token-shadow-overlay);
|
|
12
|
+
border-radius: var(--radius-lg);
|
|
13
|
+
animation: icen-popover-in 150ms var(--ease-out);
|
|
14
|
+
}
|
|
15
|
+
.popover[data-side='top'] { animation-name: icen-popover-in-top; }
|
|
16
|
+
|
|
17
|
+
/* 进场动画与 .menu 同参数:fade + scale(.95→1) + 按侧位移 8px */
|
|
18
|
+
@keyframes icen-popover-in {
|
|
19
|
+
from { opacity: 0; transform: scale(0.95) translateY(-8px); }
|
|
20
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
21
|
+
}
|
|
22
|
+
@keyframes icen-popover-in-top {
|
|
23
|
+
from { opacity: 0; transform: scale(0.95) translateY(8px); }
|
|
24
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
25
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: segmented(分段控件 / 切换组 / 数字步进器 / OTP)
|
|
3
|
+
* 规格源自 opengal primitives/input.tsx 的 SegmentedControl 与 advanced-input.tsx
|
|
4
|
+
* 的 ToggleButtonGroup / NumberStepper / OtpInput。
|
|
5
|
+
* segmented 与 toggle-group 是纯 CSS 状态组件(.is-active);stepper 由使用方接线;
|
|
6
|
+
* otp 单元格交互见 behaviors/input.ts 的 initInput。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* ── 分段控件(排他单选) ── */
|
|
10
|
+
.segmented {
|
|
11
|
+
display: inline-flex; align-items: center; gap: 2px; padding: 2px;
|
|
12
|
+
background: var(--token-panel);
|
|
13
|
+
border: 1px solid var(--token-outline-soft);
|
|
14
|
+
border-radius: var(--radius-lg);
|
|
15
|
+
box-shadow: var(--token-shadow-inset);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.segmented-item {
|
|
19
|
+
display: flex; align-items: center; gap: 6px;
|
|
20
|
+
padding: 6px 12px; font-size: 12px; font-weight: 500;
|
|
21
|
+
border: none; background: transparent; cursor: pointer;
|
|
22
|
+
border-radius: var(--radius-md);
|
|
23
|
+
color: var(--token-text-muted);
|
|
24
|
+
}
|
|
25
|
+
.segmented-item:hover:not(.is-active) {
|
|
26
|
+
color: var(--token-text); background: var(--token-surface-hover);
|
|
27
|
+
}
|
|
28
|
+
.segmented-item.is-active {
|
|
29
|
+
background: var(--token-accent); color: var(--token-on-accent, #fff);
|
|
30
|
+
box-shadow: var(--token-shadow-elevated);
|
|
31
|
+
}
|
|
32
|
+
.segmented-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
33
|
+
|
|
34
|
+
/* 尺寸:容器修饰符驱动 item 内边距 / 字号 */
|
|
35
|
+
.segmented--sm .segmented-item { padding: 4px 10px; font-size: 11px; }
|
|
36
|
+
|
|
37
|
+
/* ── 切换组(segmented 同族变体:更松的 padding、item 更小圆角、active 阴影更轻) ── */
|
|
38
|
+
.toggle-group {
|
|
39
|
+
display: inline-flex; align-items: center; gap: 4px; padding: 4px;
|
|
40
|
+
background: var(--token-panel);
|
|
41
|
+
border: 1px solid var(--token-outline-soft);
|
|
42
|
+
border-radius: var(--radius-lg);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.toggle-item {
|
|
46
|
+
display: flex; align-items: center; gap: 6px;
|
|
47
|
+
padding: 6px 12px; font-size: 12px; font-weight: 500;
|
|
48
|
+
border: none; background: transparent; cursor: pointer;
|
|
49
|
+
border-radius: var(--radius-xs);
|
|
50
|
+
color: var(--token-text-muted);
|
|
51
|
+
}
|
|
52
|
+
.toggle-item:hover:not(.is-active) {
|
|
53
|
+
color: var(--token-text); background: var(--token-control-hover);
|
|
54
|
+
}
|
|
55
|
+
.toggle-item.is-active {
|
|
56
|
+
background: var(--token-accent); color: var(--token-on-accent, #fff);
|
|
57
|
+
box-shadow: var(--token-shadow-card);
|
|
58
|
+
}
|
|
59
|
+
.toggle-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
60
|
+
.toggle-group--sm .toggle-item { padding: 4px 10px; font-size: 11px; }
|
|
61
|
+
|
|
62
|
+
/* ── 数字步进器 ── */
|
|
63
|
+
.stepper {
|
|
64
|
+
display: inline-flex; align-items: stretch;
|
|
65
|
+
background: var(--token-control);
|
|
66
|
+
border: 1px solid var(--token-outline-soft);
|
|
67
|
+
border-radius: var(--radius-lg);
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.stepper-btn {
|
|
72
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
73
|
+
padding: 0 10px; border: none; background: transparent; cursor: pointer;
|
|
74
|
+
color: var(--token-text-muted);
|
|
75
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
76
|
+
color var(--duration-normal) var(--ease-out);
|
|
77
|
+
}
|
|
78
|
+
.stepper-btn:hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
79
|
+
.stepper-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
80
|
+
|
|
81
|
+
.stepper-input {
|
|
82
|
+
width: 56px; padding: 8px 0;
|
|
83
|
+
text-align: center; font-variant-numeric: tabular-nums;
|
|
84
|
+
background: transparent; outline: none; border: none;
|
|
85
|
+
border-left: 1px solid var(--token-outline-soft);
|
|
86
|
+
border-right: 1px solid var(--token-outline-soft);
|
|
87
|
+
color: var(--token-text); font: inherit; font-size: 13px;
|
|
88
|
+
}
|
|
89
|
+
/* 隐藏原生 number 箭头(± 交给 .stepper-btn) */
|
|
90
|
+
.stepper-input::-webkit-outer-spin-button,
|
|
91
|
+
.stepper-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
92
|
+
.stepper-input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
|
|
93
|
+
|
|
94
|
+
/* ── OTP 验证码单元格 ── */
|
|
95
|
+
.otp { display: inline-flex; align-items: center; gap: 8px; }
|
|
96
|
+
|
|
97
|
+
.otp-cell {
|
|
98
|
+
width: 40px; height: 48px; padding: 0;
|
|
99
|
+
text-align: center; font-size: 18px; font-weight: 600;
|
|
100
|
+
color: var(--token-text);
|
|
101
|
+
background: var(--token-control);
|
|
102
|
+
border: 1px solid var(--token-outline-soft);
|
|
103
|
+
border-radius: var(--radius-sm);
|
|
104
|
+
outline: none;
|
|
105
|
+
transition: border-color var(--duration-normal) var(--ease-out),
|
|
106
|
+
box-shadow var(--duration-normal) var(--ease-out);
|
|
107
|
+
}
|
|
108
|
+
.otp-cell:focus-visible {
|
|
109
|
+
border-color: var(--token-accent);
|
|
110
|
+
box-shadow: 0 0 0 2px var(--token-accent-subtle);
|
|
111
|
+
}
|
|
112
|
+
.otp-cell:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: select(弹层选择器)
|
|
3
|
+
* 规格源自 opengal primitives/input.tsx 的 Select;与 behaviors/select.ts 配套。
|
|
4
|
+
*
|
|
5
|
+
* 契约:
|
|
6
|
+
* <div class="select" data-select>
|
|
7
|
+
* <button class="select-trigger pressable focus-ring" aria-haspopup="listbox" aria-expanded="false">
|
|
8
|
+
* <span class="select-leading">…</span> 可选前缀
|
|
9
|
+
* <span class="select-value is-empty">占位</span>
|
|
10
|
+
* <svg class="select-chevron">…</svg>
|
|
11
|
+
* </button>
|
|
12
|
+
* <div class="select-panel" hidden>
|
|
13
|
+
* <button class="select-option" data-value="x">…</button>
|
|
14
|
+
* </div>
|
|
15
|
+
* <input type="hidden" data-select-value /> 可选,behavior 同步 value
|
|
16
|
+
* </div>
|
|
17
|
+
* 状态:容器 .is-open;选项 .is-focused / .is-selected(尾部 ✓)/ .is-disabled;
|
|
18
|
+
* .select-value.is-empty = 未选占位(faint 色,behavior 选中后移除)。
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
.select { position: relative; width: 100%; }
|
|
22
|
+
|
|
23
|
+
.select-trigger {
|
|
24
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
25
|
+
height: 36px; padding: 0 12px; font-size: 13px; text-align: left;
|
|
26
|
+
background: var(--token-control); color: var(--token-text);
|
|
27
|
+
border: 1px solid var(--token-outline-soft);
|
|
28
|
+
border-radius: var(--radius-lg);
|
|
29
|
+
box-shadow: var(--token-shadow-inset);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
}
|
|
32
|
+
.select-trigger:focus-visible { border-color: var(--token-accent); }
|
|
33
|
+
.select-trigger:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
34
|
+
|
|
35
|
+
.select-trigger--sm { height: 32px; padding: 0 10px; font-size: 12px; }
|
|
36
|
+
.select-trigger--lg { height: 40px; padding: 0 14px; font-size: 14px; }
|
|
37
|
+
|
|
38
|
+
.select-leading {
|
|
39
|
+
display: inline-flex; align-items: center; flex-shrink: 0;
|
|
40
|
+
color: var(--token-text-faint);
|
|
41
|
+
}
|
|
42
|
+
.select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
43
|
+
.select-value.is-empty { color: var(--token-text-faint); }
|
|
44
|
+
|
|
45
|
+
.select-chevron {
|
|
46
|
+
width: 14px; height: 14px; flex-shrink: 0;
|
|
47
|
+
color: var(--token-text-faint);
|
|
48
|
+
transition: transform var(--duration-fast) var(--ease-out);
|
|
49
|
+
}
|
|
50
|
+
.select.is-open .select-chevron { transform: rotate(180deg); }
|
|
51
|
+
|
|
52
|
+
.select-panel {
|
|
53
|
+
position: absolute; top: calc(100% + 4px); left: 0;
|
|
54
|
+
min-width: 100%; max-height: 240px; overflow-y: auto; padding: 4px;
|
|
55
|
+
background: var(--token-card-strong);
|
|
56
|
+
border: 1px solid var(--token-outline);
|
|
57
|
+
border-radius: var(--radius-lg);
|
|
58
|
+
box-shadow: var(--token-shadow-overlay);
|
|
59
|
+
z-index: var(--z-popover);
|
|
60
|
+
transform-origin: top center;
|
|
61
|
+
animation: select-panel-in var(--duration-fast) var(--ease-out);
|
|
62
|
+
}
|
|
63
|
+
@keyframes select-panel-in {
|
|
64
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
65
|
+
to { opacity: 1; transform: scale(1); }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.select-option {
|
|
69
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
70
|
+
padding: 6px 10px; font-size: 12px; text-align: left;
|
|
71
|
+
border: none; background: transparent; color: var(--token-text);
|
|
72
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
73
|
+
transition: background-color var(--duration-fast) var(--ease-out),
|
|
74
|
+
color var(--duration-fast) var(--ease-out);
|
|
75
|
+
}
|
|
76
|
+
.select-option.is-focused { background: var(--token-fill-2); }
|
|
77
|
+
.select-option.is-selected { color: var(--token-accent); }
|
|
78
|
+
.select-option.is-selected::after { content: '✓'; margin-left: auto; flex-shrink: 0; }
|
|
79
|
+
.select-option.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: sidebar(侧栏导航,分组折叠配套 behaviors/sidebar)
|
|
3
|
+
* 用法:<aside class="sidebar sidebar--sticky">
|
|
4
|
+
* <div class="sidebar-group is-open">
|
|
5
|
+
* <button class="sidebar-group-title" aria-expanded="true">分组</button>
|
|
6
|
+
* <div class="sidebar-group-panel"><div class="sidebar-group-panel-inner">
|
|
7
|
+
* <a class="sidebar-item is-active" href="…">条目</a>
|
|
8
|
+
* </div></div>
|
|
9
|
+
* </div>
|
|
10
|
+
* </aside>
|
|
11
|
+
* 契约:.is-open 由 behaviors/sidebar 切换(同步 aria-expanded);
|
|
12
|
+
* 展开动画 = grid-template-rows 0fr↔1fr(同 accordion 约定),内层 overflow:hidden。
|
|
13
|
+
* 滚动条样式归 base.css 全局纪律,这里只声明 overflow-y。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
.sidebar { width: 260px; flex-shrink: 0; overflow-y: auto; padding-right: 4px; }
|
|
17
|
+
.sidebar--sticky { position: sticky; top: 80px; max-height: calc(100vh - 100px); }
|
|
18
|
+
|
|
19
|
+
.sidebar-group { margin-bottom: 8px; }
|
|
20
|
+
.sidebar-group-title {
|
|
21
|
+
width: 100%; display: flex; align-items: center; justify-content: space-between;
|
|
22
|
+
padding: 8px 12px; border-radius: var(--radius-md);
|
|
23
|
+
font-family: inherit; font-size: 11px; font-weight: 600;
|
|
24
|
+
text-transform: uppercase; letter-spacing: 1px;
|
|
25
|
+
color: var(--token-text-muted);
|
|
26
|
+
background: none; border: 0; cursor: pointer;
|
|
27
|
+
}
|
|
28
|
+
.sidebar-group-title:hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
29
|
+
/* chevron:闭合态 rotate(-90deg),展开回正 */
|
|
30
|
+
.sidebar-group-title::after {
|
|
31
|
+
content: '▾'; font-size: 12px; line-height: 1; color: var(--token-text-muted);
|
|
32
|
+
transform: rotate(-90deg);
|
|
33
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
34
|
+
}
|
|
35
|
+
.sidebar-group.is-open .sidebar-group-title::after { transform: none; }
|
|
36
|
+
|
|
37
|
+
.sidebar-group-panel {
|
|
38
|
+
display: grid; grid-template-rows: 0fr;
|
|
39
|
+
transition: grid-template-rows var(--duration-normal) var(--ease-out);
|
|
40
|
+
}
|
|
41
|
+
.sidebar-group.is-open .sidebar-group-panel { grid-template-rows: 1fr; }
|
|
42
|
+
.sidebar-group-panel-inner { overflow: hidden; min-height: 0; }
|
|
43
|
+
|
|
44
|
+
.sidebar-item {
|
|
45
|
+
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
46
|
+
padding: 8px 12px; border-radius: var(--radius-lg);
|
|
47
|
+
font-size: 13px; color: var(--token-text); text-align: left;
|
|
48
|
+
text-decoration: none; background: none; border: 0; cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
.sidebar-item:hover { background: var(--token-control-hover); text-decoration: none; }
|
|
51
|
+
.sidebar-item.is-active { background: var(--token-accent-subtle); color: var(--token-accent); font-weight: 500; }
|
|
52
|
+
.sidebar-item > svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--token-text-muted); }
|
|
53
|
+
.sidebar-item.is-active > svg { color: var(--token-accent); }
|
|
54
|
+
.sidebar-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: slider(单值 / 双滑块)
|
|
3
|
+
* 规格源自 opengal primitives/input.tsx 的 Slider 与 advanced-input.tsx 的 DualRangeSlider;
|
|
4
|
+
* 与 behaviors/slider.ts 配套(透明原生 range 覆盖层驱动 fill/thumb 定位)。
|
|
5
|
+
*
|
|
6
|
+
* 契约(单值):
|
|
7
|
+
* <div class="slider">
|
|
8
|
+
* <div class="slider-track"></div>
|
|
9
|
+
* <div class="slider-fill" style="width:40%"></div>
|
|
10
|
+
* <div class="slider-thumb" style="left:calc(40% - 8px)"></div>
|
|
11
|
+
* <input class="slider-native" type="range" />
|
|
12
|
+
* </div>
|
|
13
|
+
* 双滑块:.slider--dual 内 .slider-range(left/right 定位选中段)
|
|
14
|
+
* + .slider-thumb--lo / .slider-thumb--hi + 两个 .slider-native[data-thumb=lo|hi]
|
|
15
|
+
* label 行:.slider-row(label + 当前值,tabular-nums)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
.slider { position: relative; height: 20px; width: 100%; }
|
|
19
|
+
|
|
20
|
+
.slider-track {
|
|
21
|
+
position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
|
|
22
|
+
height: 6px; border-radius: var(--radius-full);
|
|
23
|
+
background: color-mix(in srgb, var(--token-line-soft) 50%, transparent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.slider-fill {
|
|
27
|
+
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
28
|
+
height: 6px; border-radius: var(--radius-full);
|
|
29
|
+
background: var(--token-accent);
|
|
30
|
+
transition: width var(--duration-normal) var(--ease-out);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* 双滑块选中段:left = lo%、right = (100 - hi)% */
|
|
34
|
+
.slider-range {
|
|
35
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
36
|
+
height: 6px; border-radius: var(--radius-full);
|
|
37
|
+
background: var(--token-accent);
|
|
38
|
+
transition: left var(--duration-normal) var(--ease-out),
|
|
39
|
+
right var(--duration-normal) var(--ease-out);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.slider-thumb {
|
|
43
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
44
|
+
width: 16px; height: 16px; border-radius: var(--radius-full);
|
|
45
|
+
background: var(--token-card);
|
|
46
|
+
border: 2px solid var(--token-accent);
|
|
47
|
+
box-shadow: var(--token-shadow-card);
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
transition: transform var(--duration-fast) var(--ease-out),
|
|
50
|
+
box-shadow var(--duration-fast) var(--ease-out);
|
|
51
|
+
}
|
|
52
|
+
/* 原生 input 透明,焦点环镜像到可见 thumb 上 */
|
|
53
|
+
.slider:focus-within .slider-thumb {
|
|
54
|
+
transform: translateY(-50%) scale(1.1);
|
|
55
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent),
|
|
56
|
+
0 0 0 3px var(--token-bg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.slider-native {
|
|
60
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
61
|
+
margin: 0; opacity: 0; cursor: pointer; z-index: 1;
|
|
62
|
+
}
|
|
63
|
+
.slider-native:disabled { cursor: not-allowed; }
|
|
64
|
+
|
|
65
|
+
.slider-row {
|
|
66
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
67
|
+
font-size: 12px; color: var(--token-text-muted);
|
|
68
|
+
font-variant-numeric: tabular-nums;
|
|
69
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: steps(步骤条,横向默认 / .steps--vertical 垂直)
|
|
3
|
+
* 用法:<ol class="steps">
|
|
4
|
+
* <li class="step step--finish">
|
|
5
|
+
* <span class="step-line"></span>
|
|
6
|
+
* <span class="step-dot">✓</span>
|
|
7
|
+
* <div class="step-body">
|
|
8
|
+
* <div class="step-title">已完成</div>
|
|
9
|
+
* <div class="step-desc">描述</div>
|
|
10
|
+
* </div>
|
|
11
|
+
* </li>
|
|
12
|
+
* </ol>
|
|
13
|
+
* 三态:.step--finish(accent 实底,内容放 ✓)/ .step--process(accent 描边)/ .step--wait(灰)。
|
|
14
|
+
* 连接线画在「当前步 → 下一步」之间,颜色跟随当前步状态;末步不显示(.step:last-child)。
|
|
15
|
+
* 垂直版同一 DOM:.step-line 改为绝对左侧竖线,穿过 16px 间隙接到下一颗圆点。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
.steps { display: flex; list-style: none; margin: 0; padding: 0; }
|
|
19
|
+
.step { flex: 1; position: relative; text-align: center; }
|
|
20
|
+
|
|
21
|
+
.step-line {
|
|
22
|
+
position: absolute; top: 16px; left: calc(50% + 20px);
|
|
23
|
+
width: calc(100% - 40px); height: 1px;
|
|
24
|
+
background: var(--token-outline-soft);
|
|
25
|
+
}
|
|
26
|
+
.step:last-child .step-line { display: none; }
|
|
27
|
+
.step--finish .step-line { background: color-mix(in srgb, var(--token-accent) 40%, transparent); }
|
|
28
|
+
|
|
29
|
+
.step-dot {
|
|
30
|
+
width: 32px; height: 32px; border-radius: var(--radius-full);
|
|
31
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
32
|
+
font-size: 12px; font-weight: 600; position: relative; z-index: 1;
|
|
33
|
+
}
|
|
34
|
+
.step--finish .step-dot { background: var(--token-accent); color: var(--token-on-accent); }
|
|
35
|
+
.step--process .step-dot {
|
|
36
|
+
background: var(--token-accent-subtle); color: var(--token-accent);
|
|
37
|
+
border: 2px solid var(--token-accent);
|
|
38
|
+
}
|
|
39
|
+
.step--wait .step-dot {
|
|
40
|
+
background: var(--token-control); color: var(--token-text-faint);
|
|
41
|
+
border: 1px solid var(--token-outline-soft);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.step-title { font-size: 13px; font-weight: 500; margin-top: 6px; }
|
|
45
|
+
.step--wait .step-title { color: var(--token-text-muted); }
|
|
46
|
+
.step-desc {
|
|
47
|
+
font-size: 11px; color: var(--token-text-faint);
|
|
48
|
+
margin-top: 2px; max-width: 160px; margin-inline: auto;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* ── 垂直变体 ── */
|
|
52
|
+
.steps--vertical { flex-direction: column; gap: 16px; }
|
|
53
|
+
.steps--vertical .step { flex: none; display: flex; gap: 12px; text-align: left; }
|
|
54
|
+
.steps--vertical .step-line {
|
|
55
|
+
top: 36px; bottom: -16px; left: 16px;
|
|
56
|
+
width: 1px; height: auto;
|
|
57
|
+
}
|
|
58
|
+
.steps--vertical .step-dot { flex-shrink: 0; }
|
|
59
|
+
.steps--vertical .step-body { padding-top: 4px; }
|
|
60
|
+
.steps--vertical .step-title { margin-top: 0; }
|
|
61
|
+
.steps--vertical .step-desc { max-width: none; margin-inline: 0; }
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: switch(开关 / 勾选框 / 单选行)
|
|
3
|
+
* 纯 CSS 状态组件:状态全走 aria-checked / .is-selected,交互由使用方(或演示页脚本)切换。
|
|
4
|
+
*
|
|
5
|
+
* 开关:<button class="switch [--sm]" role="switch" aria-checked="true"><span class="switch-thumb"></span></button>
|
|
6
|
+
* 勾选:<button class="checkbox focus-ring" role="checkbox" aria-checked="true|false|mixed"></button>
|
|
7
|
+
* 单选:<div class="radio-group" role="radiogroup">
|
|
8
|
+
* <button class="radio-item motion is-selected"><span class="radio-dot"></span>…</button>
|
|
9
|
+
* </div>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/* ── 开关 ── */
|
|
13
|
+
.switch {
|
|
14
|
+
position: relative; display: inline-flex; flex-shrink: 0;
|
|
15
|
+
width: 44px; height: 24px; padding: 0;
|
|
16
|
+
border: none; border-radius: var(--radius-full);
|
|
17
|
+
background: var(--token-control-hover); cursor: pointer;
|
|
18
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
19
|
+
}
|
|
20
|
+
.switch[aria-checked='true'] { background: var(--token-accent); }
|
|
21
|
+
.switch:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
22
|
+
|
|
23
|
+
.switch-thumb {
|
|
24
|
+
position: absolute; top: 2px; left: 2px;
|
|
25
|
+
width: 20px; height: 20px; border-radius: var(--radius-full);
|
|
26
|
+
background: var(--token-on-accent, #fff);
|
|
27
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
28
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
29
|
+
}
|
|
30
|
+
.switch[aria-checked='true'] .switch-thumb { transform: translateX(20px); }
|
|
31
|
+
|
|
32
|
+
.switch--sm { width: 36px; height: 20px; }
|
|
33
|
+
.switch--sm .switch-thumb { width: 16px; height: 16px; }
|
|
34
|
+
.switch--sm[aria-checked='true'] .switch-thumb { transform: translateX(16px); }
|
|
35
|
+
|
|
36
|
+
/* ── 勾选框(✓ 与半选横条均 ::before 自绘,无需内联 svg) ── */
|
|
37
|
+
.checkbox {
|
|
38
|
+
display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
39
|
+
width: 16px; height: 16px; padding: 0;
|
|
40
|
+
background: var(--token-control); color: transparent;
|
|
41
|
+
border: 1px solid var(--token-outline-soft);
|
|
42
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
43
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
44
|
+
border-color var(--duration-normal) var(--ease-out),
|
|
45
|
+
color var(--duration-normal) var(--ease-out);
|
|
46
|
+
}
|
|
47
|
+
.checkbox:hover:not([aria-checked='true']):not([aria-checked='mixed']) {
|
|
48
|
+
border-color: color-mix(in srgb, var(--token-accent) 50%, transparent);
|
|
49
|
+
}
|
|
50
|
+
.checkbox[aria-checked='true'], .checkbox[aria-checked='mixed'] {
|
|
51
|
+
background: var(--token-accent);
|
|
52
|
+
border-color: var(--token-accent);
|
|
53
|
+
color: var(--token-on-accent, #fff);
|
|
54
|
+
}
|
|
55
|
+
.checkbox::before {
|
|
56
|
+
content: ''; width: 10px; height: 10px;
|
|
57
|
+
background: currentColor; opacity: 0;
|
|
58
|
+
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
59
|
+
}
|
|
60
|
+
.checkbox[aria-checked='true']::before { opacity: 1; }
|
|
61
|
+
.checkbox[aria-checked='mixed']::before {
|
|
62
|
+
opacity: 1; width: 8px; height: 2px;
|
|
63
|
+
clip-path: none; border-radius: var(--radius-full);
|
|
64
|
+
}
|
|
65
|
+
.checkbox:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
66
|
+
|
|
67
|
+
/* ── 单选行 ── */
|
|
68
|
+
.radio-group { display: flex; flex-direction: column; gap: 4px; }
|
|
69
|
+
|
|
70
|
+
.radio-item {
|
|
71
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
72
|
+
padding: 8px 12px; font-size: 13px; text-align: left;
|
|
73
|
+
border: none; background: transparent; color: var(--token-text);
|
|
74
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
75
|
+
}
|
|
76
|
+
.radio-item.is-selected {
|
|
77
|
+
background: var(--token-accent-subtle); color: var(--token-accent); font-weight: 500;
|
|
78
|
+
}
|
|
79
|
+
.radio-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
80
|
+
|
|
81
|
+
.radio-dot {
|
|
82
|
+
position: relative; width: 16px; height: 16px; flex-shrink: 0;
|
|
83
|
+
border: 1px solid var(--token-outline-soft);
|
|
84
|
+
border-radius: var(--radius-full);
|
|
85
|
+
transition: border-color var(--duration-normal) var(--ease-out);
|
|
86
|
+
}
|
|
87
|
+
.radio-item.is-selected .radio-dot { border-color: var(--token-accent); }
|
|
88
|
+
.radio-dot::after {
|
|
89
|
+
content: ''; position: absolute; top: 50%; left: 50%;
|
|
90
|
+
width: 8px; height: 8px; border-radius: var(--radius-full);
|
|
91
|
+
background: var(--token-accent);
|
|
92
|
+
transform: translate(-50%, -50%) scale(0);
|
|
93
|
+
transition: transform var(--duration-fast) var(--ease-out);
|
|
94
|
+
}
|
|
95
|
+
.radio-item.is-selected .radio-dot::after { transform: translate(-50%, -50%) scale(1); }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: tag-input(多值标签编辑)
|
|
3
|
+
* 规格源自 opengal primitives/advanced-input.tsx 的 TagInput;与 behaviors/tag-input.ts 配套。
|
|
4
|
+
*
|
|
5
|
+
* 契约:
|
|
6
|
+
* <div class="tag-input control" data-tags="初始,标签" data-max="5">
|
|
7
|
+
* <input class="tag-input-field" placeholder="…" />
|
|
8
|
+
* </div>
|
|
9
|
+
* behavior 按 data-tags 渲染 .tag-chip(含 .tag-chip-x 删除钮),并回写 data-tags;
|
|
10
|
+
* 达到 data-max 时容器加 .is-max(warning 色边框)并拒加。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
.tag-input {
|
|
14
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
15
|
+
padding: 6px; min-height: 36px;
|
|
16
|
+
background: var(--token-control);
|
|
17
|
+
border: 1px solid var(--token-outline-soft);
|
|
18
|
+
border-radius: var(--radius-lg);
|
|
19
|
+
}
|
|
20
|
+
.tag-input:focus-within { border-color: var(--token-accent); }
|
|
21
|
+
.tag-input.is-max, .tag-input.is-max:focus-within {
|
|
22
|
+
border-color: color-mix(in srgb, var(--token-warning) 60%, transparent);
|
|
23
|
+
}
|
|
24
|
+
.tag-input.is-disabled { opacity: 0.4; }
|
|
25
|
+
.tag-input.is-disabled .tag-input-field { cursor: not-allowed; }
|
|
26
|
+
|
|
27
|
+
.tag-chip {
|
|
28
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
29
|
+
padding: 2px 4px 2px 8px;
|
|
30
|
+
border-radius: var(--radius-full);
|
|
31
|
+
background: var(--token-accent-subtle); color: var(--token-accent);
|
|
32
|
+
font-size: 11px; font-weight: 500;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.tag-chip-x {
|
|
36
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
37
|
+
padding: 2px; border: none; background: transparent; cursor: pointer;
|
|
38
|
+
border-radius: var(--radius-full); color: inherit; font-size: 11px; line-height: 1;
|
|
39
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
40
|
+
}
|
|
41
|
+
.tag-chip-x:hover { background: var(--token-fill-2); }
|
|
42
|
+
|
|
43
|
+
.tag-input-field {
|
|
44
|
+
flex: 1; min-width: 80px; padding: 4px;
|
|
45
|
+
background: transparent; border: none; outline: none;
|
|
46
|
+
color: var(--token-text); font: inherit; font-size: 12px;
|
|
47
|
+
}
|
|
48
|
+
.tag-input-field::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @icen.ai/ui — Component: tooltip(纯 CSS 气泡提示,无 JS)
|
|
3
|
+
* 契约:任意元素加 data-tooltip="文案";默认在上方,
|
|
4
|
+
* data-tooltip-side="top|bottom|left|right" 换方向。
|
|
5
|
+
* hover 延迟 400ms 出现,focus-visible 立即出现。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
[data-tooltip] { position: relative; }
|
|
9
|
+
|
|
10
|
+
[data-tooltip]::after {
|
|
11
|
+
content: attr(data-tooltip);
|
|
12
|
+
position: absolute; z-index: var(--z-tooltip);
|
|
13
|
+
width: max-content; max-width: min(260px, calc(100vw - 32px));
|
|
14
|
+
padding: 4px 8px;
|
|
15
|
+
font-size: 11px; font-weight: 400; line-height: 1.375; white-space: normal; text-align: left;
|
|
16
|
+
background: var(--token-card-strong); color: var(--token-text);
|
|
17
|
+
border: 1px solid var(--token-outline-soft);
|
|
18
|
+
box-shadow: var(--token-shadow-overlay);
|
|
19
|
+
border-radius: var(--radius-md);
|
|
20
|
+
opacity: 0; pointer-events: none;
|
|
21
|
+
transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
|
|
22
|
+
transition-delay: 400ms;
|
|
23
|
+
|
|
24
|
+
/* 默认:上方 */
|
|
25
|
+
bottom: calc(100% + 4px); left: 50%;
|
|
26
|
+
transform: translate(-50%, 4px);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[data-tooltip-side='bottom']::after {
|
|
30
|
+
bottom: auto; top: calc(100% + 4px); left: 50%;
|
|
31
|
+
transform: translate(-50%, -4px);
|
|
32
|
+
}
|
|
33
|
+
[data-tooltip-side='left']::after {
|
|
34
|
+
bottom: auto; top: 50%; left: auto; right: calc(100% + 4px);
|
|
35
|
+
transform: translate(4px, -50%);
|
|
36
|
+
}
|
|
37
|
+
[data-tooltip-side='right']::after {
|
|
38
|
+
bottom: auto; top: 50%; right: auto; left: calc(100% + 4px);
|
|
39
|
+
transform: translate(-4px, -50%);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after {
|
|
43
|
+
opacity: 1; transform: translate(-50%, 0);
|
|
44
|
+
}
|
|
45
|
+
[data-tooltip-side='bottom']:hover::after, [data-tooltip-side='bottom']:focus-visible::after {
|
|
46
|
+
opacity: 1; transform: translate(-50%, 0);
|
|
47
|
+
}
|
|
48
|
+
[data-tooltip-side='left']:hover::after, [data-tooltip-side='left']:focus-visible::after {
|
|
49
|
+
opacity: 1; transform: translate(0, -50%);
|
|
50
|
+
}
|
|
51
|
+
[data-tooltip-side='right']:hover::after, [data-tooltip-side='right']:focus-visible::after {
|
|
52
|
+
opacity: 1; transform: translate(0, -50%);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* 键盘聚焦立即出现(覆盖 400ms 延迟) */
|
|
56
|
+
[data-tooltip]:focus-visible::after { transition-delay: 0ms; }
|