@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.
- 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
package/dist/ui.css
CHANGED
|
@@ -1973,6 +1973,112 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
1973
1973
|
.dim { color: var(--token-text-muted); }
|
|
1974
1974
|
.faint { color: var(--token-text-faint); }
|
|
1975
1975
|
|
|
1976
|
+
/* ── z-index 标尺(全组件统一引用,浮层一律 portal 到 body) ── */
|
|
1977
|
+
:root {
|
|
1978
|
+
--z-base: 0;
|
|
1979
|
+
--z-raised: 10;
|
|
1980
|
+
--z-toast: 100;
|
|
1981
|
+
--z-chrome: 200;
|
|
1982
|
+
--z-dialog: 500;
|
|
1983
|
+
--z-popover: 620;
|
|
1984
|
+
--z-tooltip: 700;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/* ── 交互基元(全组件地基,规格源自 opengal interaction-classes) ── */
|
|
1988
|
+
|
|
1989
|
+
/* 可按压:hover 顶部高光 + active 下陷缩放 */
|
|
1990
|
+
.pressable {
|
|
1991
|
+
position: relative; overflow: hidden; will-change: transform;
|
|
1992
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
1993
|
+
border-color var(--duration-normal) var(--ease-out),
|
|
1994
|
+
box-shadow var(--duration-normal) var(--ease-out),
|
|
1995
|
+
color var(--duration-normal) var(--ease-out),
|
|
1996
|
+
opacity var(--duration-normal) var(--ease-out),
|
|
1997
|
+
transform var(--duration-normal) var(--ease-out);
|
|
1998
|
+
}
|
|
1999
|
+
.pressable::after {
|
|
2000
|
+
content: ''; position: absolute; inset: 0; border-radius: inherit;
|
|
2001
|
+
opacity: 0; pointer-events: none;
|
|
2002
|
+
background: linear-gradient(180deg, color-mix(in srgb, var(--token-surface) 24%, transparent), transparent 46%);
|
|
2003
|
+
transition: opacity var(--duration-normal) var(--ease-out);
|
|
2004
|
+
}
|
|
2005
|
+
.pressable:hover::after, .pressable:focus-visible::after { opacity: 1; }
|
|
2006
|
+
.pressable:active { transform: translateY(1px) scale(0.985); }
|
|
2007
|
+
.pressable:disabled, .pressable[aria-disabled='true'], .pressable[data-disabled] {
|
|
2008
|
+
transform: none;
|
|
2009
|
+
}
|
|
2010
|
+
.pressable:disabled::after, .pressable[aria-disabled='true']::after, .pressable[data-disabled]::after { display: none; }
|
|
2011
|
+
|
|
2012
|
+
/* 控件聚焦光晕(wrapper 式表单控件用 :focus-within) */
|
|
2013
|
+
.control {
|
|
2014
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
2015
|
+
border-color var(--duration-normal) var(--ease-out),
|
|
2016
|
+
box-shadow var(--duration-normal) var(--ease-out);
|
|
2017
|
+
}
|
|
2018
|
+
.control:focus-within {
|
|
2019
|
+
box-shadow: var(--token-shadow-inset), 0 0 0 3px color-mix(in srgb, var(--token-accent) 12%, transparent);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
/* 悬停浮起(可交互卡片) */
|
|
2023
|
+
.lift {
|
|
2024
|
+
transition: border-color var(--duration-slow) var(--ease-out),
|
|
2025
|
+
box-shadow var(--duration-slow) var(--ease-out),
|
|
2026
|
+
transform var(--duration-slow) var(--ease-out);
|
|
2027
|
+
}
|
|
2028
|
+
.lift:hover {
|
|
2029
|
+
transform: translateY(-1px);
|
|
2030
|
+
border-color: color-mix(in srgb, var(--token-accent) 26%, var(--token-outline-soft));
|
|
2031
|
+
box-shadow: var(--token-shadow-card);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
/* 聚焦环(ring-2 + offset-1 等价) */
|
|
2035
|
+
.focus-ring:focus-visible {
|
|
2036
|
+
outline: none;
|
|
2037
|
+
box-shadow: 0 0 0 1px var(--token-bg), 0 0 0 3px color-mix(in srgb, var(--token-accent) 60%, transparent);
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
/* 动效开关(reduced-motion 时全部关闭) */
|
|
2041
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
2042
|
+
.motion { transition: all var(--duration-normal) var(--ease-out); }
|
|
2043
|
+
}
|
|
2044
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2045
|
+
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
/* ── components/breadcrumb.css ── */
|
|
2050
|
+
/*
|
|
2051
|
+
* @icen.ai/ui — Component: breadcrumb(面包屑)
|
|
2052
|
+
* 用法:<nav class="breadcrumb" aria-label="面包屑"><ol>
|
|
2053
|
+
* <li><a class="breadcrumb-link" href="…">首页</a><span class="breadcrumb-sep" aria-hidden="true"></span></li>
|
|
2054
|
+
* <li><span class="breadcrumb-current" aria-current="page">当前页</span></li>
|
|
2055
|
+
* </ol></nav>
|
|
2056
|
+
* 分隔符是 .breadcrumb-sep 的 ::after(›);超长折叠用 .breadcrumb-ellipsis。
|
|
2057
|
+
*/
|
|
2058
|
+
|
|
2059
|
+
.breadcrumb { display: flex; align-items: center; font-size: 13px; }
|
|
2060
|
+
.breadcrumb ol { display: flex; align-items: center; gap: 6px; list-style: none; margin: 0; padding: 0; }
|
|
2061
|
+
.breadcrumb li { display: flex; align-items: center; gap: 6px; }
|
|
2062
|
+
|
|
2063
|
+
.breadcrumb-link {
|
|
2064
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
2065
|
+
color: var(--token-text-muted); text-decoration: none;
|
|
2066
|
+
transition: color var(--duration-fast) var(--ease-out);
|
|
2067
|
+
}
|
|
2068
|
+
.breadcrumb-link:hover { color: var(--token-accent); text-decoration: none; }
|
|
2069
|
+
.breadcrumb-link > svg { width: 13px; height: 13px; flex-shrink: 0; }
|
|
2070
|
+
|
|
2071
|
+
.breadcrumb-sep { color: var(--token-text-faint); font-size: 13px; line-height: 1; }
|
|
2072
|
+
.breadcrumb-sep::after { content: '›'; }
|
|
2073
|
+
|
|
2074
|
+
.breadcrumb-current {
|
|
2075
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
2076
|
+
color: var(--token-text); font-weight: 500;
|
|
2077
|
+
}
|
|
2078
|
+
.breadcrumb-current > svg { width: 13px; height: 13px; flex-shrink: 0; }
|
|
2079
|
+
|
|
2080
|
+
.breadcrumb-ellipsis { padding: 0 4px; color: var(--token-text-faint); }
|
|
2081
|
+
|
|
1976
2082
|
|
|
1977
2083
|
/* ── components/btn.css ── */
|
|
1978
2084
|
/*
|
|
@@ -2000,6 +2106,498 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2000
2106
|
.btn-sm { padding: 6px 14px; font-size: 12px; }
|
|
2001
2107
|
|
|
2002
2108
|
|
|
2109
|
+
/* ── components/card.css ── */
|
|
2110
|
+
/*
|
|
2111
|
+
* @icen.ai/ui — Component: card(组合卡片容器)
|
|
2112
|
+
* 包含:.card 四变体(默认 / elevated / outlined / glass)+ .card--interactive + .is-selected
|
|
2113
|
+
* 子结构:.card-head / .card-title / .card-desc / .card-body / .card-foot / .card-meta / .card-action
|
|
2114
|
+
* 规格源自 opengal src/presentation/ui/data/card.tsx。
|
|
2115
|
+
*/
|
|
2116
|
+
|
|
2117
|
+
.card {
|
|
2118
|
+
background: var(--token-card);
|
|
2119
|
+
border: 1px solid var(--token-outline-soft);
|
|
2120
|
+
box-shadow: var(--token-shadow-inset);
|
|
2121
|
+
border-radius: var(--radius-lg);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
/* ── 变体 ── */
|
|
2125
|
+
.card--elevated { box-shadow: var(--token-shadow-card); }
|
|
2126
|
+
.card--outlined { border-color: var(--token-outline); box-shadow: none; }
|
|
2127
|
+
.card--glass {
|
|
2128
|
+
background: var(--token-surface-glass);
|
|
2129
|
+
backdrop-filter: blur(12px);
|
|
2130
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2131
|
+
box-shadow: none;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
/* ── 修饰 ── */
|
|
2135
|
+
/* 可交互:cursor + .lift 基元同款效果(纯 CSS 无法组合类,规则在此复制一份) */
|
|
2136
|
+
.card--interactive {
|
|
2137
|
+
cursor: pointer;
|
|
2138
|
+
transition: border-color var(--duration-slow) var(--ease-out),
|
|
2139
|
+
box-shadow var(--duration-slow) var(--ease-out),
|
|
2140
|
+
transform var(--duration-slow) var(--ease-out);
|
|
2141
|
+
}
|
|
2142
|
+
.card--interactive:hover {
|
|
2143
|
+
transform: translateY(-1px);
|
|
2144
|
+
border-color: color-mix(in srgb, var(--token-accent) 26%, var(--token-outline-soft));
|
|
2145
|
+
box-shadow: var(--token-shadow-card);
|
|
2146
|
+
}
|
|
2147
|
+
.card.is-selected {
|
|
2148
|
+
box-shadow: 0 0 0 2px var(--token-accent), 0 0 0 4px var(--token-bg);
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
/* ── 子结构 ── */
|
|
2152
|
+
.card-head {
|
|
2153
|
+
display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
|
|
2154
|
+
padding: 16px;
|
|
2155
|
+
}
|
|
2156
|
+
.card-title { font-size: 15px; font-weight: 600; color: var(--token-text); line-height: 1.25; margin: 0; }
|
|
2157
|
+
.card-desc { font-size: 12px; color: var(--token-text-muted); margin: 2px 0 0; }
|
|
2158
|
+
.card-body { padding: 0 16px 16px; font-size: 13px; color: var(--token-text-muted); }
|
|
2159
|
+
.card-foot {
|
|
2160
|
+
display: flex; align-items: center; gap: 8px;
|
|
2161
|
+
padding: 12px 16px;
|
|
2162
|
+
border-top: 1px solid var(--token-outline-soft);
|
|
2163
|
+
}
|
|
2164
|
+
.card-meta { display: flex; align-items: center; gap: 8px; font-size: 11px; color: var(--token-text-faint); }
|
|
2165
|
+
.card-action { margin-left: auto; }
|
|
2166
|
+
|
|
2167
|
+
|
|
2168
|
+
/* ── components/charts.css ── */
|
|
2169
|
+
/*
|
|
2170
|
+
* @icen.ai/ui — Component: charts(轻量图表,与 behaviors/charts 配套)
|
|
2171
|
+
* 包含:.chart 容器 / .chart-empty / .chart-legend(图例行)/ .chart-labels(轴标签行)/
|
|
2172
|
+
* vbar(垂直柱状)/ hbar(水平条形)/ stack(堆叠条)/ donut(环形)/ line(折线)
|
|
2173
|
+
* DOM 全部由 behaviors/charts.ts 以 createElement(NS) + textContent 构建;
|
|
2174
|
+
* 单色调通过容器或条上的 --chart-tone 自定义属性注入(默认 var(--token-accent))。
|
|
2175
|
+
* 规格源自 opengal src/presentation/ui/data/charts.tsx。
|
|
2176
|
+
*/
|
|
2177
|
+
|
|
2178
|
+
.chart { width: 100%; }
|
|
2179
|
+
.chart-empty {
|
|
2180
|
+
height: 150px;
|
|
2181
|
+
display: flex; align-items: center; justify-content: center;
|
|
2182
|
+
font-size: 11px; color: var(--token-text-faint);
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
/* ── 图例 / 轴标签(donut / stack / vbar / line 共用) ── */
|
|
2186
|
+
.chart-legend { display: flex; flex-direction: column; gap: 6px; min-width: 0; flex: 1; }
|
|
2187
|
+
.chart-legend-item { display: flex; align-items: center; gap: 8px; font-size: 11px; min-width: 0; }
|
|
2188
|
+
.chart-legend-dot {
|
|
2189
|
+
width: 8px; height: 8px; flex-shrink: 0;
|
|
2190
|
+
border-radius: var(--radius-full);
|
|
2191
|
+
background: var(--chart-tone, var(--token-accent));
|
|
2192
|
+
}
|
|
2193
|
+
.chart-legend-label {
|
|
2194
|
+
flex: 1; min-width: 0;
|
|
2195
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
2196
|
+
color: var(--token-text-muted);
|
|
2197
|
+
}
|
|
2198
|
+
.chart-legend-value { flex-shrink: 0; font-variant-numeric: tabular-nums; color: var(--token-text-faint); }
|
|
2199
|
+
|
|
2200
|
+
.chart-labels { display: flex; gap: 8px; margin-top: 8px; }
|
|
2201
|
+
.chart-labels > span {
|
|
2202
|
+
flex: 1; min-width: 0;
|
|
2203
|
+
text-align: center; font-size: 9px; color: var(--token-text-faint);
|
|
2204
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
/* ── vbar · 垂直柱状 ── */
|
|
2208
|
+
.chart-vbar { display: flex; align-items: flex-end; gap: 8px; height: 112px; }
|
|
2209
|
+
.chart-vbar-track {
|
|
2210
|
+
flex: 1; height: 100%; min-width: 0;
|
|
2211
|
+
display: flex; align-items: flex-end;
|
|
2212
|
+
background: color-mix(in srgb, var(--token-fill-2) 45%, transparent);
|
|
2213
|
+
border-radius: var(--radius-sm);
|
|
2214
|
+
}
|
|
2215
|
+
.chart-vbar-bar {
|
|
2216
|
+
width: 100%;
|
|
2217
|
+
border-radius: var(--radius-sm);
|
|
2218
|
+
background: var(--chart-tone, var(--token-accent));
|
|
2219
|
+
transition: height 500ms var(--ease-out);
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/* ── hbar · 水平条形 ── */
|
|
2223
|
+
.chart-hbar { display: flex; flex-direction: column; gap: 8px; }
|
|
2224
|
+
.chart-hbar-row {
|
|
2225
|
+
display: grid;
|
|
2226
|
+
grid-template-columns: minmax(72px, 1fr) minmax(90px, 2fr) 52px;
|
|
2227
|
+
gap: 8px; align-items: center;
|
|
2228
|
+
font-size: 11px;
|
|
2229
|
+
}
|
|
2230
|
+
.chart-hbar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--token-text-muted); }
|
|
2231
|
+
.chart-hbar-track {
|
|
2232
|
+
height: 10px; overflow: hidden;
|
|
2233
|
+
border-radius: var(--radius-full);
|
|
2234
|
+
background: var(--token-fill-2);
|
|
2235
|
+
}
|
|
2236
|
+
.chart-hbar-bar {
|
|
2237
|
+
height: 100%;
|
|
2238
|
+
border-radius: var(--radius-full);
|
|
2239
|
+
background: var(--chart-tone, var(--token-accent));
|
|
2240
|
+
transition: width 500ms var(--ease-out);
|
|
2241
|
+
}
|
|
2242
|
+
.chart-hbar-value { text-align: right; font-variant-numeric: tabular-nums; color: var(--token-text-faint); }
|
|
2243
|
+
|
|
2244
|
+
/* ── stack · 堆叠条 ── */
|
|
2245
|
+
.chart-stack {
|
|
2246
|
+
height: 16px;
|
|
2247
|
+
display: flex; overflow: hidden;
|
|
2248
|
+
border-radius: var(--radius-full);
|
|
2249
|
+
background: var(--token-fill-2);
|
|
2250
|
+
margin-bottom: 12px;
|
|
2251
|
+
}
|
|
2252
|
+
.chart-stack-seg {
|
|
2253
|
+
height: 100%; min-width: 3px;
|
|
2254
|
+
background: var(--chart-tone, var(--token-accent));
|
|
2255
|
+
transition: width 500ms var(--ease-out);
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/* ── donut · 环形 ── */
|
|
2259
|
+
.chart-donut { display: flex; align-items: center; gap: 12px; min-height: 150px; }
|
|
2260
|
+
.chart-donut > svg { width: 132px; height: 132px; flex-shrink: 0; }
|
|
2261
|
+
|
|
2262
|
+
/* ── line · 折线 ── */
|
|
2263
|
+
.chart-line > svg { display: block; width: 100%; height: 150px; overflow: visible; }
|
|
2264
|
+
|
|
2265
|
+
|
|
2266
|
+
/* ── components/content.css ── */
|
|
2267
|
+
/*
|
|
2268
|
+
* @icen.ai/ui — Component: content(内容展示族)
|
|
2269
|
+
* 包含:kbd(按键)/ code-inline(行内代码)/ list(分隔列表)/ accordion(手风琴,behaviors/accordion 配套)/
|
|
2270
|
+
* timeline(时间线)/ desc(描述列表)/ tree(树形,behaviors/tree 配套)
|
|
2271
|
+
* 规格源自 opengal src/presentation/ui/data/exhibit.tsx 与 data/display.tsx。
|
|
2272
|
+
*/
|
|
2273
|
+
|
|
2274
|
+
/* ══════════════ kbd · 按键 ══════════════ */
|
|
2275
|
+
.kbd {
|
|
2276
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2277
|
+
vertical-align: middle;
|
|
2278
|
+
font-family: var(--font-mono); font-weight: 500;
|
|
2279
|
+
border-radius: var(--radius-xs);
|
|
2280
|
+
border: 1px solid var(--token-outline);
|
|
2281
|
+
background: var(--token-control);
|
|
2282
|
+
color: var(--token-text-muted);
|
|
2283
|
+
box-shadow: var(--token-shadow-inset);
|
|
2284
|
+
height: 20px; min-width: 20px;
|
|
2285
|
+
padding: 0 6px;
|
|
2286
|
+
font-size: 11px;
|
|
2287
|
+
}
|
|
2288
|
+
.kbd--sm { height: 16px; min-width: 16px; padding: 0 4px; font-size: 10px; }
|
|
2289
|
+
|
|
2290
|
+
/* ══════════════ code-inline · 行内代码 ══════════════ */
|
|
2291
|
+
.code-inline {
|
|
2292
|
+
padding: 2px 6px;
|
|
2293
|
+
border-radius: var(--radius-xs);
|
|
2294
|
+
background: var(--token-control);
|
|
2295
|
+
border: 1px solid var(--token-outline-soft);
|
|
2296
|
+
font-size: 12px;
|
|
2297
|
+
font-family: var(--font-mono);
|
|
2298
|
+
color: var(--token-accent);
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/* ══════════════ list · 分隔列表 ══════════════ */
|
|
2302
|
+
.list { list-style: none; margin: 0; padding: 0; }
|
|
2303
|
+
.list-item {
|
|
2304
|
+
padding: 8px;
|
|
2305
|
+
border-radius: var(--radius-md);
|
|
2306
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
2307
|
+
}
|
|
2308
|
+
.list-item:hover { background: var(--token-control-hover); }
|
|
2309
|
+
.list .list-item + .list-item { border-top: 1px solid var(--token-outline-soft); }
|
|
2310
|
+
|
|
2311
|
+
/* ══════════════ accordion · 手风琴(behaviors/accordion 配套) ══════════════
|
|
2312
|
+
* DOM 契约:
|
|
2313
|
+
* <div class="accordion" data-single?>
|
|
2314
|
+
* <div class="accordion-item">
|
|
2315
|
+
* <button class="accordion-trigger">标题 <svg class="accordion-chevron"/></button>
|
|
2316
|
+
* <div class="accordion-panel"><div class="accordion-panel-inner">内容</div></div>
|
|
2317
|
+
* </div>
|
|
2318
|
+
* </div>
|
|
2319
|
+
* 展开 = item 挂 .is-open;panel 用 grid-template-rows 0fr→1fr 做过渡。
|
|
2320
|
+
*/
|
|
2321
|
+
.accordion-item { border-bottom: 1px solid var(--token-outline-soft); }
|
|
2322
|
+
.accordion-item:last-child { border-bottom: 0; }
|
|
2323
|
+
.accordion-trigger {
|
|
2324
|
+
width: 100%;
|
|
2325
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
2326
|
+
padding: 12px 8px;
|
|
2327
|
+
font-size: 14px; font-weight: 500;
|
|
2328
|
+
color: var(--token-text); text-align: left;
|
|
2329
|
+
background: none; border: 0; cursor: pointer;
|
|
2330
|
+
border-radius: var(--radius-md);
|
|
2331
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
2332
|
+
}
|
|
2333
|
+
.accordion-trigger:hover { background: var(--token-control-hover); }
|
|
2334
|
+
.accordion-chevron {
|
|
2335
|
+
width: 14px; height: 14px; flex-shrink: 0;
|
|
2336
|
+
color: var(--token-text-faint);
|
|
2337
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
2338
|
+
}
|
|
2339
|
+
.accordion-item.is-open .accordion-chevron { transform: rotate(180deg); }
|
|
2340
|
+
.accordion-panel {
|
|
2341
|
+
display: grid;
|
|
2342
|
+
grid-template-rows: 0fr;
|
|
2343
|
+
transition: grid-template-rows var(--duration-normal) var(--ease-out);
|
|
2344
|
+
}
|
|
2345
|
+
.accordion-item.is-open .accordion-panel { grid-template-rows: 1fr; }
|
|
2346
|
+
.accordion-panel-inner {
|
|
2347
|
+
overflow: hidden;
|
|
2348
|
+
padding: 0 8px 12px;
|
|
2349
|
+
font-size: 13px;
|
|
2350
|
+
color: var(--token-text-muted);
|
|
2351
|
+
line-height: 1.6;
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
/* ══════════════ timeline · 时间线 ══════════════
|
|
2355
|
+
* DOM 契约:.timeline-item > .timeline-dot + .timeline-body
|
|
2356
|
+
* 连接线 = item 的 ::after(相对 item 绝对定位,从 dot 下方贯穿到下一项),末项不画。
|
|
2357
|
+
*/
|
|
2358
|
+
.timeline { display: flex; flex-direction: column; }
|
|
2359
|
+
.timeline-item {
|
|
2360
|
+
position: relative;
|
|
2361
|
+
display: flex; gap: 12px;
|
|
2362
|
+
padding-bottom: 24px;
|
|
2363
|
+
}
|
|
2364
|
+
.timeline-item:last-child { padding-bottom: 0; }
|
|
2365
|
+
.timeline-item:not(:last-child)::after {
|
|
2366
|
+
content: '';
|
|
2367
|
+
position: absolute;
|
|
2368
|
+
left: 13.5px; /* 28px dot 的水平中心 */
|
|
2369
|
+
top: 32px; /* 28px dot + 4px 间距 */
|
|
2370
|
+
bottom: 0;
|
|
2371
|
+
width: 1px;
|
|
2372
|
+
background: var(--token-outline-soft);
|
|
2373
|
+
}
|
|
2374
|
+
.timeline-dot {
|
|
2375
|
+
width: 28px; height: 28px; flex-shrink: 0;
|
|
2376
|
+
border-radius: var(--radius-full);
|
|
2377
|
+
display: flex; align-items: center; justify-content: center;
|
|
2378
|
+
background: var(--token-control);
|
|
2379
|
+
border: 1px solid var(--token-outline-soft);
|
|
2380
|
+
color: var(--token-text-muted);
|
|
2381
|
+
z-index: 1;
|
|
2382
|
+
}
|
|
2383
|
+
.timeline-dot svg { width: 13px; height: 13px; }
|
|
2384
|
+
.timeline-dot--accent { background: var(--token-accent); color: var(--token-on-accent); border-color: transparent; }
|
|
2385
|
+
.timeline-dot--success { background: var(--token-success); color: var(--token-on-accent); border-color: transparent; }
|
|
2386
|
+
.timeline-dot--warning { background: var(--token-warning); color: var(--token-on-accent); border-color: transparent; }
|
|
2387
|
+
.timeline-dot--error { background: var(--token-error); color: var(--token-on-accent); border-color: transparent; }
|
|
2388
|
+
.timeline-dot--info { background: var(--token-info); color: var(--token-on-accent); border-color: transparent; }
|
|
2389
|
+
.timeline-body { flex: 1; min-width: 0; }
|
|
2390
|
+
.timeline-head { display: flex; align-items: center; gap: 8px; }
|
|
2391
|
+
.timeline-title { font-size: 13px; font-weight: 500; color: var(--token-text); }
|
|
2392
|
+
.timeline-time { font-size: 11px; color: var(--token-text-faint); margin-left: auto; font-variant-numeric: tabular-nums; }
|
|
2393
|
+
.timeline-desc { font-size: 12px; color: var(--token-text-muted); margin: 4px 0 0; }
|
|
2394
|
+
|
|
2395
|
+
/* ══════════════ desc · 描述列表 ══════════════
|
|
2396
|
+
* 列数由容器 style="--desc-cols:N" 指定,默认 2;item 可用 style="--desc-span:N" 跨列。
|
|
2397
|
+
* --bordered 变体:gap:1px 露出容器底色形成格线。
|
|
2398
|
+
*/
|
|
2399
|
+
.desc { display: flex; flex-direction: column; gap: 8px; }
|
|
2400
|
+
.desc-title { font-size: 13px; font-weight: 500; color: var(--token-text); margin: 0; }
|
|
2401
|
+
.desc-grid {
|
|
2402
|
+
display: grid;
|
|
2403
|
+
grid-template-columns: repeat(var(--desc-cols, 2), minmax(0, 1fr));
|
|
2404
|
+
gap: 1px;
|
|
2405
|
+
margin: 0;
|
|
2406
|
+
}
|
|
2407
|
+
.desc--bordered .desc-grid {
|
|
2408
|
+
background: var(--token-outline-soft);
|
|
2409
|
+
border: 1px solid var(--token-outline-soft);
|
|
2410
|
+
border-radius: var(--radius-lg);
|
|
2411
|
+
overflow: hidden;
|
|
2412
|
+
}
|
|
2413
|
+
.desc-item { grid-column: span var(--desc-span, 1); }
|
|
2414
|
+
.desc--bordered .desc-item { background: var(--token-card); padding: 8px 12px; }
|
|
2415
|
+
.desc:not(.desc--bordered) .desc-item { display: flex; flex-direction: column; gap: 2px; }
|
|
2416
|
+
.desc-item dt { font-size: 11px; color: var(--token-text-faint); }
|
|
2417
|
+
.desc-item dd { font-size: 13px; color: var(--token-text); margin: 0; }
|
|
2418
|
+
|
|
2419
|
+
/* ══════════════ tree · 树形(behaviors/tree 配套) ══════════════
|
|
2420
|
+
* DOM 契约:
|
|
2421
|
+
* <ul class="tree"><li>
|
|
2422
|
+
* <div class="tree-node" style="--depth:0">
|
|
2423
|
+
* <button class="tree-toggle"><svg/></button> <!-- 叶子用空 span.tree-toggle 占 18px -->
|
|
2424
|
+
* <span class="tree-label">节点</span>
|
|
2425
|
+
* </div>
|
|
2426
|
+
* <ul>…子节点…</ul> <!-- 可选;behavior 切换 hidden -->
|
|
2427
|
+
* </li></ul>
|
|
2428
|
+
* 折叠 = toggle 挂 .is-collapsed(chevron 转 -90°)+ 子 ul hidden;选中 = node 挂 .is-selected。
|
|
2429
|
+
*/
|
|
2430
|
+
.tree, .tree ul { list-style: none; margin: 0; padding: 0; }
|
|
2431
|
+
.tree { user-select: none; }
|
|
2432
|
+
.tree-node {
|
|
2433
|
+
display: flex; align-items: center; gap: 6px;
|
|
2434
|
+
padding: 6px 8px 6px calc(4px + var(--depth, 0) * 14px);
|
|
2435
|
+
border-radius: var(--radius-md);
|
|
2436
|
+
font-size: 13px;
|
|
2437
|
+
color: var(--token-text);
|
|
2438
|
+
cursor: pointer;
|
|
2439
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
2440
|
+
}
|
|
2441
|
+
.tree-node:hover { background: var(--token-control-hover); }
|
|
2442
|
+
.tree-node.is-selected {
|
|
2443
|
+
background: var(--token-accent-subtle);
|
|
2444
|
+
color: var(--token-accent);
|
|
2445
|
+
font-weight: 500;
|
|
2446
|
+
}
|
|
2447
|
+
.tree-node.is-disabled { opacity: 0.4; pointer-events: none; }
|
|
2448
|
+
.tree-toggle {
|
|
2449
|
+
width: 18px; height: 18px; flex-shrink: 0;
|
|
2450
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2451
|
+
padding: 0; border: 0; background: none;
|
|
2452
|
+
color: var(--token-text-muted);
|
|
2453
|
+
cursor: pointer;
|
|
2454
|
+
}
|
|
2455
|
+
.tree-toggle:hover { color: var(--token-text); }
|
|
2456
|
+
.tree-toggle svg {
|
|
2457
|
+
width: 13px; height: 13px;
|
|
2458
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
2459
|
+
}
|
|
2460
|
+
.tree-toggle.is-collapsed svg { transform: rotate(-90deg); }
|
|
2461
|
+
.tree-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2462
|
+
|
|
2463
|
+
|
|
2464
|
+
/* ── components/empty.css ── */
|
|
2465
|
+
/*
|
|
2466
|
+
* @icen.ai/ui — Component: empty(空状态)
|
|
2467
|
+
* 包含:.empty 容器 + .empty-icon / .empty-title / .empty-desc / .empty-action
|
|
2468
|
+
* 规格源自 opengal src/presentation/ui/data/display.tsx 的 EmptyState。
|
|
2469
|
+
*/
|
|
2470
|
+
|
|
2471
|
+
.empty {
|
|
2472
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
2473
|
+
text-align: center;
|
|
2474
|
+
padding: 48px 16px;
|
|
2475
|
+
}
|
|
2476
|
+
.empty-icon {
|
|
2477
|
+
width: 48px; height: 48px;
|
|
2478
|
+
border-radius: var(--radius-lg);
|
|
2479
|
+
background: var(--token-card);
|
|
2480
|
+
border: 1px solid var(--token-outline-soft);
|
|
2481
|
+
box-shadow: var(--token-shadow-inset);
|
|
2482
|
+
display: flex; align-items: center; justify-content: center;
|
|
2483
|
+
color: var(--token-text-faint);
|
|
2484
|
+
margin-bottom: 16px;
|
|
2485
|
+
}
|
|
2486
|
+
.empty-icon svg { width: 24px; height: 24px; }
|
|
2487
|
+
.empty-title { font-size: 14px; font-weight: 500; color: var(--token-text); margin: 0; }
|
|
2488
|
+
.empty-desc { font-size: 12px; color: var(--token-text-muted); margin: 4px 0 0; max-width: 240px; }
|
|
2489
|
+
.empty-action { margin-top: 16px; }
|
|
2490
|
+
|
|
2491
|
+
|
|
2492
|
+
/* ── components/feedback.css ── */
|
|
2493
|
+
/*
|
|
2494
|
+
* @icen.ai/ui — Component: feedback(反馈族)
|
|
2495
|
+
* 包含:alert(四态内联横幅)/ result(终态页)/ progress(线性进度)/ spinner(加载圈)/ skeleton(占位块)
|
|
2496
|
+
* 规格源自 opengal src/presentation/ui/feedback/toast.tsx 的 Alert 与 data/exhibit.tsx 的 Result、
|
|
2497
|
+
* data/display.tsx 的 Progress / Spinner / Skeleton。
|
|
2498
|
+
* base.css 未提供 spin / skeleton-pulse 关键帧,在本文件自带。
|
|
2499
|
+
*/
|
|
2500
|
+
|
|
2501
|
+
/* ══════════════ alert · 内联横幅 ══════════════ */
|
|
2502
|
+
.alert {
|
|
2503
|
+
display: flex; align-items: flex-start; gap: 12px;
|
|
2504
|
+
padding: 12px;
|
|
2505
|
+
border-radius: var(--radius-lg);
|
|
2506
|
+
box-shadow: var(--token-shadow-inset);
|
|
2507
|
+
border: 1px solid transparent;
|
|
2508
|
+
}
|
|
2509
|
+
.alert--info { background: var(--token-info-muted); border-color: color-mix(in srgb, var(--token-info) 15%, transparent); }
|
|
2510
|
+
.alert--success { background: var(--token-success-muted); border-color: color-mix(in srgb, var(--token-success) 15%, transparent); }
|
|
2511
|
+
.alert--warning { background: var(--token-warning-muted); border-color: color-mix(in srgb, var(--token-warning) 15%, transparent); }
|
|
2512
|
+
.alert--error { background: var(--token-error-muted); border-color: color-mix(in srgb, var(--token-error) 15%, transparent); }
|
|
2513
|
+
|
|
2514
|
+
.alert-icon { flex-shrink: 0; margin-top: 2px; display: flex; }
|
|
2515
|
+
.alert-icon svg { width: 16px; height: 16px; }
|
|
2516
|
+
.alert--info .alert-icon, .alert--info .alert-title { color: var(--token-info); }
|
|
2517
|
+
.alert--success .alert-icon, .alert--success .alert-title { color: var(--token-success); }
|
|
2518
|
+
.alert--warning .alert-icon, .alert--warning .alert-title { color: var(--token-warning); }
|
|
2519
|
+
.alert--error .alert-icon, .alert--error .alert-title { color: var(--token-error); }
|
|
2520
|
+
|
|
2521
|
+
.alert-content { flex: 1; min-width: 0; }
|
|
2522
|
+
.alert-title { font-size: 13px; font-weight: 500; margin: 0; }
|
|
2523
|
+
.alert-body { font-size: 12px; color: var(--token-text-muted); line-height: 1.6; }
|
|
2524
|
+
.alert-close {
|
|
2525
|
+
margin-left: auto; flex-shrink: 0;
|
|
2526
|
+
display: flex; align-items: center; justify-content: center;
|
|
2527
|
+
padding: 2px; border: 0; border-radius: var(--radius-xs);
|
|
2528
|
+
background: none; color: var(--token-text-faint); cursor: pointer;
|
|
2529
|
+
}
|
|
2530
|
+
.alert-close:hover { color: var(--token-text); background: color-mix(in srgb, var(--token-text) 6%, transparent); }
|
|
2531
|
+
.alert-close svg { width: 14px; height: 14px; }
|
|
2532
|
+
|
|
2533
|
+
/* ══════════════ result · 终态页 ══════════════ */
|
|
2534
|
+
.result {
|
|
2535
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
2536
|
+
text-align: center;
|
|
2537
|
+
padding: 40px 24px;
|
|
2538
|
+
}
|
|
2539
|
+
.result-icon {
|
|
2540
|
+
width: 64px; height: 64px;
|
|
2541
|
+
border-radius: var(--radius-full);
|
|
2542
|
+
display: flex; align-items: center; justify-content: center;
|
|
2543
|
+
box-shadow: var(--token-shadow-inset);
|
|
2544
|
+
border: 1px solid transparent;
|
|
2545
|
+
margin-bottom: 16px;
|
|
2546
|
+
}
|
|
2547
|
+
.result-icon svg { width: 28px; height: 28px; }
|
|
2548
|
+
.result--success .result-icon { background: var(--token-success-muted); color: var(--token-success); border-color: color-mix(in srgb, var(--token-success) 10%, transparent); }
|
|
2549
|
+
.result--error .result-icon { background: var(--token-error-muted); color: var(--token-error); border-color: color-mix(in srgb, var(--token-error) 10%, transparent); }
|
|
2550
|
+
.result--warning .result-icon { background: var(--token-warning-muted); color: var(--token-warning); border-color: color-mix(in srgb, var(--token-warning) 10%, transparent); }
|
|
2551
|
+
.result--info .result-icon { background: var(--token-info-muted); color: var(--token-info); border-color: color-mix(in srgb, var(--token-info) 10%, transparent); }
|
|
2552
|
+
.result-title { font-size: 17px; font-weight: 600; color: var(--token-text); margin: 0; }
|
|
2553
|
+
.result-desc { font-size: 13px; color: var(--token-text-muted); margin: 4px 0 0; max-width: 360px; }
|
|
2554
|
+
.result-extra { display: flex; align-items: center; gap: 8px; margin-top: 20px; }
|
|
2555
|
+
|
|
2556
|
+
/* ══════════════ progress · 线性进度 ══════════════ */
|
|
2557
|
+
.progress { display: flex; align-items: center; gap: 8px; }
|
|
2558
|
+
.progress-track {
|
|
2559
|
+
flex: 1; height: 6px; overflow: hidden;
|
|
2560
|
+
border-radius: var(--radius-full);
|
|
2561
|
+
background: color-mix(in srgb, var(--token-line-soft) 40%, transparent);
|
|
2562
|
+
}
|
|
2563
|
+
.progress-bar {
|
|
2564
|
+
height: 100%;
|
|
2565
|
+
border-radius: var(--radius-full);
|
|
2566
|
+
background: var(--token-accent);
|
|
2567
|
+
transition: width 500ms var(--ease-out);
|
|
2568
|
+
}
|
|
2569
|
+
.progress--sm .progress-track { height: 4px; }
|
|
2570
|
+
.progress-label { font-size: 11px; color: var(--token-text-muted); font-variant-numeric: tabular-nums; }
|
|
2571
|
+
|
|
2572
|
+
/* ══════════════ spinner · 加载圈 ══════════════ */
|
|
2573
|
+
.spinner {
|
|
2574
|
+
width: 20px; height: 20px;
|
|
2575
|
+
border: 2px solid var(--token-accent);
|
|
2576
|
+
border-top-color: transparent;
|
|
2577
|
+
border-radius: var(--radius-full);
|
|
2578
|
+
animation: icen-spin 1s linear infinite;
|
|
2579
|
+
}
|
|
2580
|
+
.spinner--sm { width: 16px; height: 16px; }
|
|
2581
|
+
.spinner--lg { width: 32px; height: 32px; }
|
|
2582
|
+
|
|
2583
|
+
@keyframes icen-spin {
|
|
2584
|
+
to { transform: rotate(360deg); }
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
/* ══════════════ skeleton · 占位块 ══════════════ */
|
|
2588
|
+
.skeleton {
|
|
2589
|
+
background: var(--token-control-hover);
|
|
2590
|
+
border-radius: var(--radius-sm);
|
|
2591
|
+
animation: icen-skeleton-pulse 2s ease-in-out infinite;
|
|
2592
|
+
}
|
|
2593
|
+
.skeleton--circle { border-radius: var(--radius-full); }
|
|
2594
|
+
|
|
2595
|
+
@keyframes icen-skeleton-pulse {
|
|
2596
|
+
0%, 100% { opacity: 1; }
|
|
2597
|
+
50% { opacity: 0.5; }
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
|
|
2003
2601
|
/* ── components/form.css ── */
|
|
2004
2602
|
/*
|
|
2005
2603
|
* @icen.ai/ui — Component: form(表单字段/输入/错误与成功提示)
|
|
@@ -2034,6 +2632,634 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2034
2632
|
.banner-ok, .banner-err { margin-bottom: 20px; }
|
|
2035
2633
|
|
|
2036
2634
|
|
|
2635
|
+
/* ── components/input.css ── */
|
|
2636
|
+
/*
|
|
2637
|
+
* @icen.ai/ui — Component: input(wrapper 式输入框 / 多行 / 搜索)
|
|
2638
|
+
* 规格源自 opengal primitives/input.tsx;与 behaviors/input.ts 配套。
|
|
2639
|
+
*
|
|
2640
|
+
* 契约:
|
|
2641
|
+
* <div class="input-wrap control [--sm|--lg] [.is-error] [.is-disabled]">
|
|
2642
|
+
* <span class="input-leading">…</span> 可选前缀
|
|
2643
|
+
* <input class="input" />
|
|
2644
|
+
* <button class="input-clear">…</button> 可选清除钮(behavior 清空并聚焦)
|
|
2645
|
+
* <button class="input-pw-toggle">…</button> 可选密码可见切换(behavior 驱动)
|
|
2646
|
+
* <span class="input-trailing">…</span> 可选后缀
|
|
2647
|
+
* </div>
|
|
2648
|
+
* 多行:<div class="textarea-wrap control"><textarea class="textarea" data-autosize></textarea></div>
|
|
2649
|
+
* 搜索:.input-wrap--search = leading 放大镜 + .input-clear 的结构约定(无额外视觉)
|
|
2650
|
+
*/
|
|
2651
|
+
|
|
2652
|
+
.input-wrap {
|
|
2653
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
2654
|
+
height: 36px; padding: 0 12px; font-size: 13px;
|
|
2655
|
+
background: var(--token-control);
|
|
2656
|
+
border: 1px solid var(--token-outline-soft);
|
|
2657
|
+
border-radius: var(--radius-lg);
|
|
2658
|
+
box-shadow: var(--token-shadow-inset);
|
|
2659
|
+
}
|
|
2660
|
+
.input-wrap:focus-within { border-color: var(--token-accent); }
|
|
2661
|
+
.input-wrap.is-error, .input-wrap.is-error:focus-within { border-color: var(--token-error); }
|
|
2662
|
+
.input-wrap.is-disabled { opacity: 0.4; }
|
|
2663
|
+
.input-wrap.is-disabled .input { cursor: not-allowed; }
|
|
2664
|
+
|
|
2665
|
+
/* 尺寸三档:高度 / 横向内边距 / 字号 */
|
|
2666
|
+
.input-wrap--sm { height: 32px; padding: 0 10px; font-size: 12px; }
|
|
2667
|
+
.input-wrap--lg { height: 40px; padding: 0 14px; font-size: 14px; }
|
|
2668
|
+
|
|
2669
|
+
.input {
|
|
2670
|
+
flex: 1; min-width: 0; padding: 0;
|
|
2671
|
+
background: transparent; border: none; outline: none;
|
|
2672
|
+
color: var(--token-text); font: inherit;
|
|
2673
|
+
}
|
|
2674
|
+
.input::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
|
|
2675
|
+
|
|
2676
|
+
.input-leading, .input-trailing {
|
|
2677
|
+
display: inline-flex; align-items: center; flex-shrink: 0;
|
|
2678
|
+
color: var(--token-text-faint);
|
|
2679
|
+
}
|
|
2680
|
+
|
|
2681
|
+
.input-clear {
|
|
2682
|
+
display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
2683
|
+
padding: 2px; border: none; background: transparent; cursor: pointer;
|
|
2684
|
+
border-radius: var(--radius-full); color: var(--token-text-faint);
|
|
2685
|
+
transition: color var(--duration-normal) var(--ease-out),
|
|
2686
|
+
background-color var(--duration-normal) var(--ease-out);
|
|
2687
|
+
}
|
|
2688
|
+
.input-clear:hover { color: var(--token-text); background: var(--token-surface-hover); }
|
|
2689
|
+
|
|
2690
|
+
.input-pw-toggle {
|
|
2691
|
+
display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
2692
|
+
padding: 2px; border: none; background: transparent; cursor: pointer;
|
|
2693
|
+
color: var(--token-text-faint);
|
|
2694
|
+
transition: color var(--duration-normal) var(--ease-out);
|
|
2695
|
+
}
|
|
2696
|
+
.input-pw-toggle:hover { color: var(--token-text); }
|
|
2697
|
+
|
|
2698
|
+
/* ── 多行:同一 wrapper 视觉,内部 textarea 裸奔 ── */
|
|
2699
|
+
.textarea-wrap {
|
|
2700
|
+
display: flex; width: 100%;
|
|
2701
|
+
background: var(--token-control);
|
|
2702
|
+
border: 1px solid var(--token-outline-soft);
|
|
2703
|
+
border-radius: var(--radius-lg);
|
|
2704
|
+
box-shadow: var(--token-shadow-inset);
|
|
2705
|
+
}
|
|
2706
|
+
.textarea-wrap:focus-within { border-color: var(--token-accent); }
|
|
2707
|
+
.textarea-wrap.is-error, .textarea-wrap.is-error:focus-within { border-color: var(--token-error); }
|
|
2708
|
+
.textarea-wrap.is-disabled { opacity: 0.4; }
|
|
2709
|
+
.textarea-wrap.is-disabled .textarea { cursor: not-allowed; }
|
|
2710
|
+
|
|
2711
|
+
.textarea {
|
|
2712
|
+
width: 100%; padding: 12px;
|
|
2713
|
+
background: transparent; border: none; outline: none; resize: none;
|
|
2714
|
+
color: var(--token-text); font: inherit; font-size: 13px; line-height: 1.5;
|
|
2715
|
+
min-height: calc(3 * 1.5em + 24px); /* 3 行 + 上下 padding */
|
|
2716
|
+
}
|
|
2717
|
+
.textarea::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
|
|
2718
|
+
|
|
2719
|
+
|
|
2720
|
+
/* ── components/layout.css ── */
|
|
2721
|
+
/*
|
|
2722
|
+
* @icen.ai/ui — Component: layout(布局原语,全部是工具类,无 JS)
|
|
2723
|
+
* stack / divider / surface / text / grid / center / container / aspect / visually-hidden
|
|
2724
|
+
* 全部消费 --token-*;档位类名与 opengal primitives/layout(-containers) 一一对应。
|
|
2725
|
+
*/
|
|
2726
|
+
|
|
2727
|
+
/* ── Stack:flex 堆叠(默认列向,gap 8px) ── */
|
|
2728
|
+
.stack { display: flex; flex-direction: column; gap: 8px; }
|
|
2729
|
+
.stack--row { flex-direction: row; }
|
|
2730
|
+
.stack--inline { display: inline-flex; }
|
|
2731
|
+
.stack--wrap { flex-wrap: wrap; }
|
|
2732
|
+
.stack--gap-0 { gap: 0; }
|
|
2733
|
+
.stack--gap-1 { gap: 4px; }
|
|
2734
|
+
.stack--gap-2 { gap: 8px; }
|
|
2735
|
+
.stack--gap-3 { gap: 12px; }
|
|
2736
|
+
.stack--gap-4 { gap: 16px; }
|
|
2737
|
+
.stack--gap-6 { gap: 24px; }
|
|
2738
|
+
.stack--gap-8 { gap: 32px; }
|
|
2739
|
+
.stack--gap-12 { gap: 48px; }
|
|
2740
|
+
.stack--gap-16 { gap: 64px; }
|
|
2741
|
+
.stack--items-start { align-items: flex-start; }
|
|
2742
|
+
.stack--items-center { align-items: center; }
|
|
2743
|
+
.stack--items-end { align-items: flex-end; }
|
|
2744
|
+
.stack--items-stretch { align-items: stretch; }
|
|
2745
|
+
.stack--items-baseline { align-items: baseline; }
|
|
2746
|
+
.stack--justify-start { justify-content: flex-start; }
|
|
2747
|
+
.stack--justify-center { justify-content: center; }
|
|
2748
|
+
.stack--justify-end { justify-content: flex-end; }
|
|
2749
|
+
.stack--justify-between { justify-content: space-between; }
|
|
2750
|
+
.stack--justify-around { justify-content: space-around; }
|
|
2751
|
+
.stack--justify-evenly { justify-content: space-evenly; }
|
|
2752
|
+
|
|
2753
|
+
/* ── Divider:分隔线 ── */
|
|
2754
|
+
.divider { background: var(--token-outline-soft); height: 1px; width: 100%; }
|
|
2755
|
+
.divider--vertical { width: 1px; height: 100%; align-self: stretch; }
|
|
2756
|
+
|
|
2757
|
+
/* ── Surface:卡片容器(默认 inset 阴影 + 16px padding) ── */
|
|
2758
|
+
.surface {
|
|
2759
|
+
border-radius: var(--radius-lg);
|
|
2760
|
+
background: var(--token-card);
|
|
2761
|
+
border: 1px solid var(--token-outline-soft);
|
|
2762
|
+
box-shadow: var(--token-shadow-inset);
|
|
2763
|
+
padding: 16px;
|
|
2764
|
+
}
|
|
2765
|
+
.surface--elevated { box-shadow: var(--token-shadow-card); }
|
|
2766
|
+
.surface--overlay {
|
|
2767
|
+
background: var(--token-card-strong);
|
|
2768
|
+
border-color: var(--token-outline);
|
|
2769
|
+
box-shadow: var(--token-shadow-overlay);
|
|
2770
|
+
}
|
|
2771
|
+
.surface--p-none { padding: 0; }
|
|
2772
|
+
.surface--p-sm { padding: 12px; }
|
|
2773
|
+
.surface--p-md { padding: 16px; }
|
|
2774
|
+
.surface--p-lg { padding: 20px; }
|
|
2775
|
+
|
|
2776
|
+
/* ── Text:字号 / 语义色 / 截断 ── */
|
|
2777
|
+
.text--xs { font-size: 11px; }
|
|
2778
|
+
.text--sm { font-size: 12px; }
|
|
2779
|
+
.text--base { font-size: 13px; }
|
|
2780
|
+
.text--md { font-size: 14px; }
|
|
2781
|
+
.text--lg { font-size: 15px; }
|
|
2782
|
+
.text--xl { font-size: 18px; }
|
|
2783
|
+
.text--2xl { font-size: 22px; }
|
|
2784
|
+
.text--muted { color: var(--token-text-muted); }
|
|
2785
|
+
.text--faint { color: var(--token-text-faint); }
|
|
2786
|
+
.text--accent { color: var(--token-accent); }
|
|
2787
|
+
.text--success { color: var(--token-success); }
|
|
2788
|
+
.text--warning { color: var(--token-warning); }
|
|
2789
|
+
.text--error { color: var(--token-error); }
|
|
2790
|
+
.text--truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2791
|
+
|
|
2792
|
+
/* ── Grid:栅格(默认 gap 12px) ── */
|
|
2793
|
+
.grid { display: grid; gap: 12px; }
|
|
2794
|
+
.grid--cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
2795
|
+
.grid--cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
2796
|
+
.grid--cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
2797
|
+
.grid--cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
2798
|
+
.grid--cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
2799
|
+
.grid--cols-12 { grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
|
2800
|
+
.grid--gap-0 { gap: 0; }
|
|
2801
|
+
.grid--gap-4 { gap: 4px; }
|
|
2802
|
+
.grid--gap-8 { gap: 8px; }
|
|
2803
|
+
.grid--gap-12 { gap: 12px; }
|
|
2804
|
+
.grid--gap-16 { gap: 16px; }
|
|
2805
|
+
.grid--gap-24 { gap: 24px; }
|
|
2806
|
+
|
|
2807
|
+
/* ── Center:flex 居中盒 ── */
|
|
2808
|
+
.center { display: flex; align-items: center; justify-content: center; }
|
|
2809
|
+
|
|
2810
|
+
/* ── Container:限宽容器(默认 960px) ── */
|
|
2811
|
+
.container { width: 100%; margin-inline: auto; padding-inline: 16px; max-width: 960px; }
|
|
2812
|
+
.container--sm { max-width: 480px; }
|
|
2813
|
+
.container--md { max-width: 720px; }
|
|
2814
|
+
.container--lg { max-width: 960px; }
|
|
2815
|
+
.container--xl { max-width: 1200px; }
|
|
2816
|
+
.container--full { max-width: none; }
|
|
2817
|
+
|
|
2818
|
+
/* ── Aspect:固定比例盒(padding-bottom hack,内容 absolute inset:0) ── */
|
|
2819
|
+
.aspect { position: relative; width: 100%; overflow: hidden; padding-bottom: 56.25%; }
|
|
2820
|
+
.aspect--square { padding-bottom: 100%; }
|
|
2821
|
+
.aspect--4-3 { padding-bottom: 75%; }
|
|
2822
|
+
.aspect--2-3 { padding-bottom: 150%; }
|
|
2823
|
+
.aspect > * { position: absolute; inset: 0; }
|
|
2824
|
+
|
|
2825
|
+
/* ── VisuallyHidden:屏幕阅读器专用文本(sr-only 等价) ── */
|
|
2826
|
+
.visually-hidden {
|
|
2827
|
+
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
|
|
2828
|
+
overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
|
|
2832
|
+
/* ── components/media.css ── */
|
|
2833
|
+
/*
|
|
2834
|
+
* @icen.ai/ui — Component: media(媒体族)
|
|
2835
|
+
* 包含:avatar(头像)/ media-card(封面卡,滑出 overlay)/ rating(星级评分)/ carousel(轮播,与 behaviors/carousel 配套)
|
|
2836
|
+
* carousel 归入本文件(无独立 carousel.css 配额);规格源自 opengal data/display.tsx 的 Avatar、
|
|
2837
|
+
* data/cartridge-card.tsx 的 hover overlay、data/exhibit.tsx 的 Carousel 与 Rating。
|
|
2838
|
+
*/
|
|
2839
|
+
|
|
2840
|
+
/* ══════════════ avatar · 头像 ══════════════ */
|
|
2841
|
+
.avatar {
|
|
2842
|
+
position: relative;
|
|
2843
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2844
|
+
overflow: hidden;
|
|
2845
|
+
border-radius: var(--radius-full);
|
|
2846
|
+
background: var(--token-accent-subtle);
|
|
2847
|
+
color: var(--token-accent);
|
|
2848
|
+
font-weight: 600;
|
|
2849
|
+
width: 36px; height: 36px; font-size: 12px;
|
|
2850
|
+
flex-shrink: 0;
|
|
2851
|
+
}
|
|
2852
|
+
.avatar--xs { width: 20px; height: 20px; font-size: 9px; }
|
|
2853
|
+
.avatar--sm { width: 28px; height: 28px; font-size: 10px; }
|
|
2854
|
+
.avatar--lg { width: 48px; height: 48px; font-size: 14px; }
|
|
2855
|
+
.avatar img { width: 100%; height: 100%; object-fit: cover; }
|
|
2856
|
+
|
|
2857
|
+
/* ══════════════ media-card · 封面卡 ══════════════ */
|
|
2858
|
+
.media-card {
|
|
2859
|
+
position: relative;
|
|
2860
|
+
overflow: hidden;
|
|
2861
|
+
border-radius: var(--radius-lg);
|
|
2862
|
+
transition: transform 300ms var(--ease-out), box-shadow 300ms var(--ease-out);
|
|
2863
|
+
}
|
|
2864
|
+
.media-card:hover {
|
|
2865
|
+
transform: translateY(-6px);
|
|
2866
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2), 0 0 20px var(--token-accent-glow);
|
|
2867
|
+
}
|
|
2868
|
+
.media-card-cover {
|
|
2869
|
+
display: block;
|
|
2870
|
+
width: 100%;
|
|
2871
|
+
aspect-ratio: 2 / 3;
|
|
2872
|
+
object-fit: cover;
|
|
2873
|
+
}
|
|
2874
|
+
.media-card-overlay {
|
|
2875
|
+
position: absolute; left: 0; right: 0; bottom: 0;
|
|
2876
|
+
padding: 32px 12px 10px;
|
|
2877
|
+
background: linear-gradient(to top, rgba(0, 0, 0, 0.72), rgba(0, 0, 0, 0.35) 65%, transparent);
|
|
2878
|
+
backdrop-filter: blur(10px);
|
|
2879
|
+
-webkit-backdrop-filter: blur(10px);
|
|
2880
|
+
color: #fff;
|
|
2881
|
+
transform: translateY(100%);
|
|
2882
|
+
transition: transform 200ms var(--ease-out);
|
|
2883
|
+
pointer-events: none;
|
|
2884
|
+
}
|
|
2885
|
+
.media-card:hover .media-card-overlay {
|
|
2886
|
+
transform: translateY(0);
|
|
2887
|
+
transition-delay: 80ms;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
/* ══════════════ rating · 星级评分 ══════════════ */
|
|
2891
|
+
.rating { display: inline-flex; align-items: center; gap: 2px; }
|
|
2892
|
+
.rating-star {
|
|
2893
|
+
padding: 2px;
|
|
2894
|
+
border-radius: var(--radius-xs);
|
|
2895
|
+
background: none; border: 0;
|
|
2896
|
+
cursor: pointer;
|
|
2897
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
2898
|
+
}
|
|
2899
|
+
.rating-star svg {
|
|
2900
|
+
width: 18px; height: 18px; display: block;
|
|
2901
|
+
fill: transparent;
|
|
2902
|
+
stroke: var(--token-line-strong);
|
|
2903
|
+
stroke-width: 1.6;
|
|
2904
|
+
transition: fill var(--duration-normal) var(--ease-out), stroke var(--duration-normal) var(--ease-out);
|
|
2905
|
+
}
|
|
2906
|
+
.rating--sm .rating-star svg { width: 14px; height: 14px; }
|
|
2907
|
+
.rating--lg .rating-star svg { width: 22px; height: 22px; }
|
|
2908
|
+
.rating-star.is-filled svg { fill: var(--token-warning); stroke: var(--token-warning); }
|
|
2909
|
+
.rating-star.is-half svg {
|
|
2910
|
+
fill: color-mix(in srgb, var(--token-warning) 50%, transparent);
|
|
2911
|
+
stroke: var(--token-warning);
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
/* ══════════════ carousel · 轮播(behaviors/carousel 配套) ══════════════
|
|
2915
|
+
* DOM 契约:
|
|
2916
|
+
* <div class="carousel">
|
|
2917
|
+
* <div class="carousel-track"><div class="carousel-slide">…</div>×n</div>
|
|
2918
|
+
* <button class="carousel-arrow carousel-arrow--prev">…</button>
|
|
2919
|
+
* <button class="carousel-arrow carousel-arrow--next">…</button>
|
|
2920
|
+
* <div class="carousel-dots"></div> <!-- dots 由 behavior 自动生成 -->
|
|
2921
|
+
* </div>
|
|
2922
|
+
*/
|
|
2923
|
+
.carousel {
|
|
2924
|
+
position: relative;
|
|
2925
|
+
overflow: hidden;
|
|
2926
|
+
border-radius: var(--radius-lg);
|
|
2927
|
+
background: var(--token-control);
|
|
2928
|
+
}
|
|
2929
|
+
.carousel-track {
|
|
2930
|
+
display: flex;
|
|
2931
|
+
transition: transform 300ms var(--ease-out);
|
|
2932
|
+
}
|
|
2933
|
+
.carousel-slide { width: 100%; flex-shrink: 0; }
|
|
2934
|
+
.carousel-arrow {
|
|
2935
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
2936
|
+
width: 32px; height: 32px;
|
|
2937
|
+
border-radius: var(--radius-full);
|
|
2938
|
+
background: color-mix(in srgb, var(--token-card) 78%, transparent);
|
|
2939
|
+
border: 1px solid var(--token-outline-soft);
|
|
2940
|
+
color: var(--token-text);
|
|
2941
|
+
display: flex; align-items: center; justify-content: center;
|
|
2942
|
+
backdrop-filter: blur(4px);
|
|
2943
|
+
-webkit-backdrop-filter: blur(4px);
|
|
2944
|
+
box-shadow: var(--token-shadow-card);
|
|
2945
|
+
cursor: pointer;
|
|
2946
|
+
transition: background-color var(--duration-normal) var(--ease-out), transform var(--duration-normal) var(--ease-out);
|
|
2947
|
+
}
|
|
2948
|
+
.carousel-arrow:hover { background: var(--token-card-strong); }
|
|
2949
|
+
.carousel-arrow:active { transform: translateY(-50%) scale(0.95); }
|
|
2950
|
+
.carousel-arrow svg { width: 16px; height: 16px; }
|
|
2951
|
+
.carousel-arrow--prev { left: 8px; }
|
|
2952
|
+
.carousel-arrow--next { right: 8px; }
|
|
2953
|
+
.carousel-dots {
|
|
2954
|
+
position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%);
|
|
2955
|
+
display: flex; gap: 6px;
|
|
2956
|
+
}
|
|
2957
|
+
.carousel-dot {
|
|
2958
|
+
width: 6px; height: 6px; padding: 0;
|
|
2959
|
+
border: 0; border-radius: var(--radius-full);
|
|
2960
|
+
background: color-mix(in srgb, var(--token-card) 70%, transparent);
|
|
2961
|
+
cursor: pointer;
|
|
2962
|
+
transition: width var(--duration-normal) var(--ease-out), background-color var(--duration-normal) var(--ease-out);
|
|
2963
|
+
}
|
|
2964
|
+
.carousel-dot:hover { background: color-mix(in srgb, var(--token-accent) 70%, transparent); }
|
|
2965
|
+
.carousel-dot.active { width: 20px; background: var(--token-accent); }
|
|
2966
|
+
|
|
2967
|
+
|
|
2968
|
+
/* ── components/menu.css ── */
|
|
2969
|
+
/*
|
|
2970
|
+
* @icen.ai/ui — Component: menu(菜单面板,与 behaviors/dropdown、context-menu 配套)
|
|
2971
|
+
* 面板由 JS portal 到 document.body 并按 fixed 定位;data-side 决定进场位移方向。
|
|
2972
|
+
* 规格源自 opengal overlay/dropdown + overlay/context-menu。
|
|
2973
|
+
*/
|
|
2974
|
+
|
|
2975
|
+
.menu {
|
|
2976
|
+
position: fixed; z-index: var(--z-popover);
|
|
2977
|
+
min-width: 180px; max-height: min(70vh, 420px); overflow-y: auto;
|
|
2978
|
+
margin: 0; padding: 4px;
|
|
2979
|
+
background: color-mix(in srgb, var(--token-card-strong) 92%, transparent);
|
|
2980
|
+
backdrop-filter: blur(24px);
|
|
2981
|
+
border: 1px solid var(--token-outline);
|
|
2982
|
+
box-shadow: var(--token-shadow-overlay);
|
|
2983
|
+
border-radius: var(--radius-lg);
|
|
2984
|
+
animation: icen-menu-in 150ms var(--ease-out);
|
|
2985
|
+
}
|
|
2986
|
+
.menu[data-side='top'] { animation-name: icen-menu-in-top; }
|
|
2987
|
+
|
|
2988
|
+
/* 右键菜单修正(opengal context-menu:min 218 / max 286 / z 9999 / 入场 100ms) */
|
|
2989
|
+
.menu--context {
|
|
2990
|
+
min-width: 218px; max-width: 286px; z-index: 9999;
|
|
2991
|
+
animation-duration: 100ms;
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
/* ── 分组标签 / 头(context-menu 的 header 项) ── */
|
|
2995
|
+
.menu-label {
|
|
2996
|
+
padding: 4px 8px; font-size: 10px; font-weight: 600;
|
|
2997
|
+
text-transform: uppercase; letter-spacing: 0.08em;
|
|
2998
|
+
color: var(--token-text-faint);
|
|
2999
|
+
}
|
|
3000
|
+
.menu-header { padding: 8px 10px 6px; }
|
|
3001
|
+
.menu-header-label {
|
|
3002
|
+
display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3003
|
+
font-size: 11px; font-weight: 500; line-height: 16px; color: var(--token-text);
|
|
3004
|
+
}
|
|
3005
|
+
.menu-header-desc {
|
|
3006
|
+
display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3007
|
+
font-size: 10px; line-height: 14px; color: var(--token-text-faint);
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
/* ── 菜单项 ── */
|
|
3011
|
+
.menu-item {
|
|
3012
|
+
position: relative; display: flex; align-items: center; gap: 8px;
|
|
3013
|
+
width: 100%; padding: 6px 8px;
|
|
3014
|
+
background: none; border: 0; font: inherit; font-size: 12px; text-align: left;
|
|
3015
|
+
color: var(--token-text);
|
|
3016
|
+
border-radius: var(--radius-md); cursor: pointer; user-select: none;
|
|
3017
|
+
}
|
|
3018
|
+
.menu-item.is-highlighted, .menu-item:focus {
|
|
3019
|
+
background: var(--token-surface-hover); color: var(--token-accent); outline: none;
|
|
3020
|
+
}
|
|
3021
|
+
.menu-item--danger { color: var(--token-error); }
|
|
3022
|
+
.menu-item--danger.is-highlighted, .menu-item--danger:focus {
|
|
3023
|
+
background: var(--token-error-muted); color: var(--token-error);
|
|
3024
|
+
}
|
|
3025
|
+
.menu-item.is-disabled, .menu-item:disabled { opacity: 0.5; pointer-events: none; }
|
|
3026
|
+
|
|
3027
|
+
.menu-item-icon {
|
|
3028
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3029
|
+
width: 14px; height: 14px; flex-shrink: 0;
|
|
3030
|
+
}
|
|
3031
|
+
.menu-item-icon:empty { width: 16px; height: auto; } /* 无图标时 16px 宽占位 */
|
|
3032
|
+
.menu-item-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
3033
|
+
.menu-item-sub { display: block; font-size: 10px; line-height: 1.2; color: var(--token-text-faint); }
|
|
3034
|
+
.menu-item-shortcut { font-size: 10px; color: var(--token-text-faint); }
|
|
3035
|
+
|
|
3036
|
+
/* 勾选项:左勾 ✓ + 缩进 */
|
|
3037
|
+
.menu-item-check {
|
|
3038
|
+
position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
|
|
3039
|
+
color: var(--token-accent); font-size: 12px; line-height: 1;
|
|
3040
|
+
}
|
|
3041
|
+
.menu-item--checked { padding-left: 28px; }
|
|
3042
|
+
|
|
3043
|
+
.menu-separator { margin: 6px 8px; height: 1px; background: var(--token-line-soft); }
|
|
3044
|
+
|
|
3045
|
+
/* ── 进场:fade + scale(.95→1),bottom 从上方 8px 滑入、top 从下方 8px 滑入 ── */
|
|
3046
|
+
@keyframes icen-menu-in {
|
|
3047
|
+
from { opacity: 0; transform: scale(0.95) translateY(-8px); }
|
|
3048
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
3049
|
+
}
|
|
3050
|
+
@keyframes icen-menu-in-top {
|
|
3051
|
+
from { opacity: 0; transform: scale(0.95) translateY(8px); }
|
|
3052
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
3053
|
+
}
|
|
3054
|
+
|
|
3055
|
+
|
|
3056
|
+
/* ── components/modal.css ── */
|
|
3057
|
+
/*
|
|
3058
|
+
* @icen.ai/ui — Component: modal(对话框 + 贴边 Sheet,与 behaviors/modal 配套)
|
|
3059
|
+
* 契约:<div class="modal-backdrop" data-modal="id" hidden> > .modal[role=dialog]
|
|
3060
|
+
* 规格源自 opengal overlay/dialog:backdrop fadeIn 150ms、面板 dialogScaleIn
|
|
3061
|
+
* 200ms cubic-bezier(.16,1,.3,1)、Sheet 四向 slideIn 200ms 位移 40px。
|
|
3062
|
+
*/
|
|
3063
|
+
|
|
3064
|
+
.modal-backdrop {
|
|
3065
|
+
position: fixed; inset: 0; z-index: var(--z-dialog);
|
|
3066
|
+
display: flex; align-items: center; justify-content: center; padding: 16px;
|
|
3067
|
+
background: rgba(0, 0, 0, 0.45); backdrop-filter: blur(4px);
|
|
3068
|
+
animation: icen-modal-fade-in 150ms ease-out;
|
|
3069
|
+
}
|
|
3070
|
+
.modal-backdrop[hidden] { display: none; }
|
|
3071
|
+
|
|
3072
|
+
.modal {
|
|
3073
|
+
display: flex; flex-direction: column; overflow: hidden;
|
|
3074
|
+
width: 100%; max-width: 480px; max-height: calc(100vh - 32px);
|
|
3075
|
+
background: var(--token-card-strong); color: var(--token-text);
|
|
3076
|
+
border: 1px solid var(--token-outline);
|
|
3077
|
+
box-shadow: var(--token-shadow-overlay);
|
|
3078
|
+
border-radius: var(--radius-xl);
|
|
3079
|
+
animation: icen-modal-scale-in 200ms cubic-bezier(0.16, 1, 0.3, 1);
|
|
3080
|
+
}
|
|
3081
|
+
.modal:focus { outline: none; }
|
|
3082
|
+
|
|
3083
|
+
/* ── 尺寸档 ── */
|
|
3084
|
+
.modal--sm { max-width: 360px; }
|
|
3085
|
+
.modal--lg { max-width: 640px; }
|
|
3086
|
+
.modal--xl { max-width: 800px; }
|
|
3087
|
+
.modal--full { width: calc(100vw - 32px); max-width: none; height: calc(100vh - 32px); max-height: none; }
|
|
3088
|
+
|
|
3089
|
+
/* ── 头部 / 主体 / 底栏 ── */
|
|
3090
|
+
.modal-header {
|
|
3091
|
+
display: flex; align-items: flex-start; justify-content: space-between;
|
|
3092
|
+
gap: 16px; padding: 20px 24px 12px;
|
|
3093
|
+
}
|
|
3094
|
+
.modal-title { margin: 0; font-size: 15px; font-weight: 600; }
|
|
3095
|
+
.modal-description { margin: 2px 0 0; font-size: 12px; color: var(--token-text-muted); }
|
|
3096
|
+
.modal-close {
|
|
3097
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3098
|
+
width: 28px; height: 28px; padding: 0; flex-shrink: 0;
|
|
3099
|
+
background: none; border: 0; cursor: pointer;
|
|
3100
|
+
color: var(--token-text-faint); font-size: 16px; line-height: 1;
|
|
3101
|
+
border-radius: var(--radius-md);
|
|
3102
|
+
}
|
|
3103
|
+
.modal-close:hover { background: var(--token-surface-hover); color: var(--token-text); }
|
|
3104
|
+
|
|
3105
|
+
.modal-body { flex: 1; overflow-y: auto; padding: 0 24px 20px; }
|
|
3106
|
+
.modal-footer {
|
|
3107
|
+
display: flex; justify-content: flex-end; gap: 8px; padding: 16px 24px;
|
|
3108
|
+
border-top: 1px solid color-mix(in srgb, var(--token-line-soft) 40%, transparent);
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
/* ── Sheet 变体:面板 fixed 贴边,radius 只保留非贴边侧 ── */
|
|
3112
|
+
.modal--sheet-left, .modal--sheet-right, .modal--sheet-top, .modal--sheet-bottom {
|
|
3113
|
+
position: fixed; max-width: none; max-height: none; border-radius: 0;
|
|
3114
|
+
}
|
|
3115
|
+
.modal--sheet-left {
|
|
3116
|
+
left: 0; top: 0; bottom: 0; width: 360px; height: 100%;
|
|
3117
|
+
border-radius: 0 var(--radius-xl) var(--radius-xl) 0;
|
|
3118
|
+
animation-name: icen-modal-slide-left;
|
|
3119
|
+
}
|
|
3120
|
+
.modal--sheet-right {
|
|
3121
|
+
right: 0; top: 0; bottom: 0; width: 360px; height: 100%;
|
|
3122
|
+
border-radius: var(--radius-xl) 0 0 var(--radius-xl);
|
|
3123
|
+
animation-name: icen-modal-slide-right;
|
|
3124
|
+
}
|
|
3125
|
+
.modal--sheet-top {
|
|
3126
|
+
top: 0; left: 0; right: 0; width: 100%; height: 300px;
|
|
3127
|
+
border-radius: 0 0 var(--radius-xl) var(--radius-xl);
|
|
3128
|
+
animation-name: icen-modal-slide-top;
|
|
3129
|
+
}
|
|
3130
|
+
.modal--sheet-bottom {
|
|
3131
|
+
bottom: 0; left: 0; right: 0; width: 100%; height: 300px;
|
|
3132
|
+
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
|
3133
|
+
animation-name: icen-modal-slide-bottom;
|
|
3134
|
+
}
|
|
3135
|
+
.modal--sheet-left.modal--sheet-sm, .modal--sheet-right.modal--sheet-sm { width: 280px; }
|
|
3136
|
+
.modal--sheet-left.modal--sheet-lg, .modal--sheet-right.modal--sheet-lg { width: 480px; }
|
|
3137
|
+
.modal--sheet-top.modal--sheet-lg, .modal--sheet-bottom.modal--sheet-lg { height: 50vh; }
|
|
3138
|
+
|
|
3139
|
+
/* ── 进场动画(与 opengal src/index.css 同参数) ── */
|
|
3140
|
+
@keyframes icen-modal-fade-in {
|
|
3141
|
+
from { opacity: 0; }
|
|
3142
|
+
to { opacity: 1; }
|
|
3143
|
+
}
|
|
3144
|
+
@keyframes icen-modal-scale-in {
|
|
3145
|
+
from { opacity: 0; transform: scale(0.96) translateY(8px); }
|
|
3146
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
3147
|
+
}
|
|
3148
|
+
@keyframes icen-modal-slide-left {
|
|
3149
|
+
from { opacity: 0; transform: translateX(-40px); }
|
|
3150
|
+
to { opacity: 1; transform: translateX(0); }
|
|
3151
|
+
}
|
|
3152
|
+
@keyframes icen-modal-slide-right {
|
|
3153
|
+
from { opacity: 0; transform: translateX(40px); }
|
|
3154
|
+
to { opacity: 1; transform: translateX(0); }
|
|
3155
|
+
}
|
|
3156
|
+
@keyframes icen-modal-slide-top {
|
|
3157
|
+
from { opacity: 0; transform: translateY(-40px); }
|
|
3158
|
+
to { opacity: 1; transform: translateY(0); }
|
|
3159
|
+
}
|
|
3160
|
+
@keyframes icen-modal-slide-bottom {
|
|
3161
|
+
from { opacity: 0; transform: translateY(40px); }
|
|
3162
|
+
to { opacity: 1; transform: translateY(0); }
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
|
|
3166
|
+
/* ── components/nav.css ── */
|
|
3167
|
+
/*
|
|
3168
|
+
* @icen.ai/ui — Component: nav(玻璃顶栏,scroll 收缩配套 behaviors/nav)
|
|
3169
|
+
* 用法:<nav class="nav nav--sticky" data-scroll-reactive>
|
|
3170
|
+
* <div class="nav-left">…</div><div class="nav-center">…</div><div class="nav-right">…</div>
|
|
3171
|
+
* </nav>
|
|
3172
|
+
* 三段:.nav-left / .nav-center(flex:1 居中)/ .nav-right,整行补充用 .nav-bottom。
|
|
3173
|
+
* 按钮:.nav-btn(HTML 组合 .motion .pressable 基元),.is-active / .is-disabled / .nav-btn--icon。
|
|
3174
|
+
* 契约:sticky/fixed 挂 --z-chrome 层;data-scroll-reactive 时 behaviors/nav 依 scrollY>12 切
|
|
3175
|
+
* .is-scrolled——未滚动更透且无边线,滚动后底色更实 + blur 16px。
|
|
3176
|
+
*/
|
|
3177
|
+
|
|
3178
|
+
.nav {
|
|
3179
|
+
display: flex; align-items: center; flex-wrap: wrap; gap: 12px;
|
|
3180
|
+
height: 56px; padding: 0 16px;
|
|
3181
|
+
background: var(--token-surface-glass);
|
|
3182
|
+
backdrop-filter: blur(12px) saturate(1.1);
|
|
3183
|
+
-webkit-backdrop-filter: blur(12px) saturate(1.1);
|
|
3184
|
+
border-bottom: 1px solid var(--token-outline-soft);
|
|
3185
|
+
transition: all var(--duration-slow) var(--ease-out);
|
|
3186
|
+
}
|
|
3187
|
+
/* 带整行 bottom 时放开固定高度,第一行仍保持 56px */
|
|
3188
|
+
.nav:has(.nav-bottom) { height: auto; min-height: 56px; }
|
|
3189
|
+
.nav--sticky { position: sticky; top: 0; z-index: var(--z-chrome); }
|
|
3190
|
+
.nav--fixed { position: fixed; top: 0; left: 0; right: 0; z-index: var(--z-chrome); }
|
|
3191
|
+
|
|
3192
|
+
/* scroll-reactive:未滚动更透、底边线透明(滚动后由 .is-scrolled 接管) */
|
|
3193
|
+
.nav[data-scroll-reactive]:not(.is-scrolled) {
|
|
3194
|
+
background: color-mix(in srgb, var(--token-surface-glass) 70%, transparent);
|
|
3195
|
+
backdrop-filter: blur(8px);
|
|
3196
|
+
-webkit-backdrop-filter: blur(8px);
|
|
3197
|
+
border-bottom-color: transparent;
|
|
3198
|
+
}
|
|
3199
|
+
.nav.is-scrolled {
|
|
3200
|
+
background: color-mix(in srgb, var(--token-surface-glass) 92%, transparent);
|
|
3201
|
+
backdrop-filter: blur(16px) saturate(1.2);
|
|
3202
|
+
-webkit-backdrop-filter: blur(16px) saturate(1.2);
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
.nav-left, .nav-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
|
|
3206
|
+
.nav-center { flex: 1; display: flex; align-items: center; justify-content: center; gap: 4px; }
|
|
3207
|
+
.nav-bottom { width: 100%; }
|
|
3208
|
+
|
|
3209
|
+
.nav-btn {
|
|
3210
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
3211
|
+
border-radius: var(--radius-full); padding: 6px 12px;
|
|
3212
|
+
font-size: 13px; color: var(--token-text-muted);
|
|
3213
|
+
background: none; border: 0; cursor: pointer;
|
|
3214
|
+
}
|
|
3215
|
+
.nav-btn > svg { width: 14px; height: 14px; flex-shrink: 0; }
|
|
3216
|
+
.nav-btn:not(.is-active):hover { color: var(--token-text); background: var(--token-control-hover); }
|
|
3217
|
+
.nav-btn.is-active {
|
|
3218
|
+
background: var(--token-accent); color: var(--token-on-accent);
|
|
3219
|
+
font-weight: 500; box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
|
3220
|
+
}
|
|
3221
|
+
.nav-btn.is-disabled, .nav-btn:disabled { opacity: .4; pointer-events: none; }
|
|
3222
|
+
.nav-btn--icon { padding: 6px 10px; }
|
|
3223
|
+
.nav-btn--icon > svg { width: 15px; height: 15px; }
|
|
3224
|
+
|
|
3225
|
+
.nav-divider { width: 1px; height: 20px; margin: 0 2px; background: var(--token-outline-soft); }
|
|
3226
|
+
|
|
3227
|
+
|
|
3228
|
+
/* ── components/pagination.css ── */
|
|
3229
|
+
/*
|
|
3230
|
+
* @icen.ai/ui — Component: pagination(分页)
|
|
3231
|
+
* 用法:<nav class="pagination" aria-label="分页">
|
|
3232
|
+
* <button class="page-btn motion is-disabled">‹</button>
|
|
3233
|
+
* <button class="page-btn motion">1</button>
|
|
3234
|
+
* <span class="page-ellipsis">…</span>
|
|
3235
|
+
* <button class="page-btn motion is-active" aria-current="page">5</button>
|
|
3236
|
+
* <button class="page-btn motion">›</button>
|
|
3237
|
+
* </nav>
|
|
3238
|
+
* 翻页逻辑由消费方实现,这里只提供静态样式契约。
|
|
3239
|
+
*/
|
|
3240
|
+
|
|
3241
|
+
.pagination { display: flex; align-items: center; gap: 4px; }
|
|
3242
|
+
|
|
3243
|
+
.page-btn {
|
|
3244
|
+
min-width: 28px; height: 28px; padding: 0 6px;
|
|
3245
|
+
border-radius: var(--radius-md); font-size: 12px;
|
|
3246
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3247
|
+
color: var(--token-text-muted); background: none; border: 0; cursor: pointer;
|
|
3248
|
+
}
|
|
3249
|
+
.page-btn:not(.is-active):hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
3250
|
+
.page-btn.is-active {
|
|
3251
|
+
background: var(--token-accent); color: var(--token-on-accent);
|
|
3252
|
+
font-weight: 500; box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
|
|
3253
|
+
}
|
|
3254
|
+
.page-btn.is-disabled, .page-btn:disabled { opacity: .3; pointer-events: none; }
|
|
3255
|
+
|
|
3256
|
+
.page-ellipsis {
|
|
3257
|
+
min-width: 28px; height: 28px; font-size: 12px;
|
|
3258
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3259
|
+
color: var(--token-text-faint);
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
|
|
2037
3263
|
/* ── components/panel.css ── */
|
|
2038
3264
|
/*
|
|
2039
3265
|
* @icen.ai/ui — Component: panel(白卡片容器 + 标题栏)
|
|
@@ -2087,6 +3313,360 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2087
3313
|
}
|
|
2088
3314
|
|
|
2089
3315
|
|
|
3316
|
+
/* ── components/popover.css ── */
|
|
3317
|
+
/*
|
|
3318
|
+
* @icen.ai/ui — Component: popover(锚定浮层,与 behaviors/popover 配套)
|
|
3319
|
+
* 面板由 JS portal 到 document.body 并按 fixed 定位;data-side 由布局函数写入。
|
|
3320
|
+
*/
|
|
3321
|
+
|
|
3322
|
+
.popover {
|
|
3323
|
+
position: fixed; z-index: var(--z-popover);
|
|
3324
|
+
padding: 8px;
|
|
3325
|
+
background: var(--token-card-strong); color: var(--token-text);
|
|
3326
|
+
border: 1px solid var(--token-outline);
|
|
3327
|
+
box-shadow: var(--token-shadow-overlay);
|
|
3328
|
+
border-radius: var(--radius-lg);
|
|
3329
|
+
animation: icen-popover-in 150ms var(--ease-out);
|
|
3330
|
+
}
|
|
3331
|
+
.popover[data-side='top'] { animation-name: icen-popover-in-top; }
|
|
3332
|
+
|
|
3333
|
+
/* 进场动画与 .menu 同参数:fade + scale(.95→1) + 按侧位移 8px */
|
|
3334
|
+
@keyframes icen-popover-in {
|
|
3335
|
+
from { opacity: 0; transform: scale(0.95) translateY(-8px); }
|
|
3336
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
3337
|
+
}
|
|
3338
|
+
@keyframes icen-popover-in-top {
|
|
3339
|
+
from { opacity: 0; transform: scale(0.95) translateY(8px); }
|
|
3340
|
+
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
3341
|
+
}
|
|
3342
|
+
|
|
3343
|
+
|
|
3344
|
+
/* ── components/segmented.css ── */
|
|
3345
|
+
/*
|
|
3346
|
+
* @icen.ai/ui — Component: segmented(分段控件 / 切换组 / 数字步进器 / OTP)
|
|
3347
|
+
* 规格源自 opengal primitives/input.tsx 的 SegmentedControl 与 advanced-input.tsx
|
|
3348
|
+
* 的 ToggleButtonGroup / NumberStepper / OtpInput。
|
|
3349
|
+
* segmented 与 toggle-group 是纯 CSS 状态组件(.is-active);stepper 由使用方接线;
|
|
3350
|
+
* otp 单元格交互见 behaviors/input.ts 的 initInput。
|
|
3351
|
+
*/
|
|
3352
|
+
|
|
3353
|
+
/* ── 分段控件(排他单选) ── */
|
|
3354
|
+
.segmented {
|
|
3355
|
+
display: inline-flex; align-items: center; gap: 2px; padding: 2px;
|
|
3356
|
+
background: var(--token-panel);
|
|
3357
|
+
border: 1px solid var(--token-outline-soft);
|
|
3358
|
+
border-radius: var(--radius-lg);
|
|
3359
|
+
box-shadow: var(--token-shadow-inset);
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
.segmented-item {
|
|
3363
|
+
display: flex; align-items: center; gap: 6px;
|
|
3364
|
+
padding: 6px 12px; font-size: 12px; font-weight: 500;
|
|
3365
|
+
border: none; background: transparent; cursor: pointer;
|
|
3366
|
+
border-radius: var(--radius-md);
|
|
3367
|
+
color: var(--token-text-muted);
|
|
3368
|
+
}
|
|
3369
|
+
.segmented-item:hover:not(.is-active) {
|
|
3370
|
+
color: var(--token-text); background: var(--token-surface-hover);
|
|
3371
|
+
}
|
|
3372
|
+
.segmented-item.is-active {
|
|
3373
|
+
background: var(--token-accent); color: var(--token-on-accent, #fff);
|
|
3374
|
+
box-shadow: var(--token-shadow-elevated);
|
|
3375
|
+
}
|
|
3376
|
+
.segmented-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3377
|
+
|
|
3378
|
+
/* 尺寸:容器修饰符驱动 item 内边距 / 字号 */
|
|
3379
|
+
.segmented--sm .segmented-item { padding: 4px 10px; font-size: 11px; }
|
|
3380
|
+
|
|
3381
|
+
/* ── 切换组(segmented 同族变体:更松的 padding、item 更小圆角、active 阴影更轻) ── */
|
|
3382
|
+
.toggle-group {
|
|
3383
|
+
display: inline-flex; align-items: center; gap: 4px; padding: 4px;
|
|
3384
|
+
background: var(--token-panel);
|
|
3385
|
+
border: 1px solid var(--token-outline-soft);
|
|
3386
|
+
border-radius: var(--radius-lg);
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
.toggle-item {
|
|
3390
|
+
display: flex; align-items: center; gap: 6px;
|
|
3391
|
+
padding: 6px 12px; font-size: 12px; font-weight: 500;
|
|
3392
|
+
border: none; background: transparent; cursor: pointer;
|
|
3393
|
+
border-radius: var(--radius-xs);
|
|
3394
|
+
color: var(--token-text-muted);
|
|
3395
|
+
}
|
|
3396
|
+
.toggle-item:hover:not(.is-active) {
|
|
3397
|
+
color: var(--token-text); background: var(--token-control-hover);
|
|
3398
|
+
}
|
|
3399
|
+
.toggle-item.is-active {
|
|
3400
|
+
background: var(--token-accent); color: var(--token-on-accent, #fff);
|
|
3401
|
+
box-shadow: var(--token-shadow-card);
|
|
3402
|
+
}
|
|
3403
|
+
.toggle-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3404
|
+
.toggle-group--sm .toggle-item { padding: 4px 10px; font-size: 11px; }
|
|
3405
|
+
|
|
3406
|
+
/* ── 数字步进器 ── */
|
|
3407
|
+
.stepper {
|
|
3408
|
+
display: inline-flex; align-items: stretch;
|
|
3409
|
+
background: var(--token-control);
|
|
3410
|
+
border: 1px solid var(--token-outline-soft);
|
|
3411
|
+
border-radius: var(--radius-lg);
|
|
3412
|
+
overflow: hidden;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
.stepper-btn {
|
|
3416
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3417
|
+
padding: 0 10px; border: none; background: transparent; cursor: pointer;
|
|
3418
|
+
color: var(--token-text-muted);
|
|
3419
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
3420
|
+
color var(--duration-normal) var(--ease-out);
|
|
3421
|
+
}
|
|
3422
|
+
.stepper-btn:hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
3423
|
+
.stepper-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3424
|
+
|
|
3425
|
+
.stepper-input {
|
|
3426
|
+
width: 56px; padding: 8px 0;
|
|
3427
|
+
text-align: center; font-variant-numeric: tabular-nums;
|
|
3428
|
+
background: transparent; outline: none; border: none;
|
|
3429
|
+
border-left: 1px solid var(--token-outline-soft);
|
|
3430
|
+
border-right: 1px solid var(--token-outline-soft);
|
|
3431
|
+
color: var(--token-text); font: inherit; font-size: 13px;
|
|
3432
|
+
}
|
|
3433
|
+
/* 隐藏原生 number 箭头(± 交给 .stepper-btn) */
|
|
3434
|
+
.stepper-input::-webkit-outer-spin-button,
|
|
3435
|
+
.stepper-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
3436
|
+
.stepper-input[type='number'] { -moz-appearance: textfield; appearance: textfield; }
|
|
3437
|
+
|
|
3438
|
+
/* ── OTP 验证码单元格 ── */
|
|
3439
|
+
.otp { display: inline-flex; align-items: center; gap: 8px; }
|
|
3440
|
+
|
|
3441
|
+
.otp-cell {
|
|
3442
|
+
width: 40px; height: 48px; padding: 0;
|
|
3443
|
+
text-align: center; font-size: 18px; font-weight: 600;
|
|
3444
|
+
color: var(--token-text);
|
|
3445
|
+
background: var(--token-control);
|
|
3446
|
+
border: 1px solid var(--token-outline-soft);
|
|
3447
|
+
border-radius: var(--radius-sm);
|
|
3448
|
+
outline: none;
|
|
3449
|
+
transition: border-color var(--duration-normal) var(--ease-out),
|
|
3450
|
+
box-shadow var(--duration-normal) var(--ease-out);
|
|
3451
|
+
}
|
|
3452
|
+
.otp-cell:focus-visible {
|
|
3453
|
+
border-color: var(--token-accent);
|
|
3454
|
+
box-shadow: 0 0 0 2px var(--token-accent-subtle);
|
|
3455
|
+
}
|
|
3456
|
+
.otp-cell:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3457
|
+
|
|
3458
|
+
|
|
3459
|
+
/* ── components/select.css ── */
|
|
3460
|
+
/*
|
|
3461
|
+
* @icen.ai/ui — Component: select(弹层选择器)
|
|
3462
|
+
* 规格源自 opengal primitives/input.tsx 的 Select;与 behaviors/select.ts 配套。
|
|
3463
|
+
*
|
|
3464
|
+
* 契约:
|
|
3465
|
+
* <div class="select" data-select>
|
|
3466
|
+
* <button class="select-trigger pressable focus-ring" aria-haspopup="listbox" aria-expanded="false">
|
|
3467
|
+
* <span class="select-leading">…</span> 可选前缀
|
|
3468
|
+
* <span class="select-value is-empty">占位</span>
|
|
3469
|
+
* <svg class="select-chevron">…</svg>
|
|
3470
|
+
* </button>
|
|
3471
|
+
* <div class="select-panel" hidden>
|
|
3472
|
+
* <button class="select-option" data-value="x">…</button>
|
|
3473
|
+
* </div>
|
|
3474
|
+
* <input type="hidden" data-select-value /> 可选,behavior 同步 value
|
|
3475
|
+
* </div>
|
|
3476
|
+
* 状态:容器 .is-open;选项 .is-focused / .is-selected(尾部 ✓)/ .is-disabled;
|
|
3477
|
+
* .select-value.is-empty = 未选占位(faint 色,behavior 选中后移除)。
|
|
3478
|
+
*/
|
|
3479
|
+
|
|
3480
|
+
.select { position: relative; width: 100%; }
|
|
3481
|
+
|
|
3482
|
+
.select-trigger {
|
|
3483
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
3484
|
+
height: 36px; padding: 0 12px; font-size: 13px; text-align: left;
|
|
3485
|
+
background: var(--token-control); color: var(--token-text);
|
|
3486
|
+
border: 1px solid var(--token-outline-soft);
|
|
3487
|
+
border-radius: var(--radius-lg);
|
|
3488
|
+
box-shadow: var(--token-shadow-inset);
|
|
3489
|
+
cursor: pointer;
|
|
3490
|
+
}
|
|
3491
|
+
.select-trigger:focus-visible { border-color: var(--token-accent); }
|
|
3492
|
+
.select-trigger:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3493
|
+
|
|
3494
|
+
.select-trigger--sm { height: 32px; padding: 0 10px; font-size: 12px; }
|
|
3495
|
+
.select-trigger--lg { height: 40px; padding: 0 14px; font-size: 14px; }
|
|
3496
|
+
|
|
3497
|
+
.select-leading {
|
|
3498
|
+
display: inline-flex; align-items: center; flex-shrink: 0;
|
|
3499
|
+
color: var(--token-text-faint);
|
|
3500
|
+
}
|
|
3501
|
+
.select-value { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3502
|
+
.select-value.is-empty { color: var(--token-text-faint); }
|
|
3503
|
+
|
|
3504
|
+
.select-chevron {
|
|
3505
|
+
width: 14px; height: 14px; flex-shrink: 0;
|
|
3506
|
+
color: var(--token-text-faint);
|
|
3507
|
+
transition: transform var(--duration-fast) var(--ease-out);
|
|
3508
|
+
}
|
|
3509
|
+
.select.is-open .select-chevron { transform: rotate(180deg); }
|
|
3510
|
+
|
|
3511
|
+
.select-panel {
|
|
3512
|
+
position: absolute; top: calc(100% + 4px); left: 0;
|
|
3513
|
+
min-width: 100%; max-height: 240px; overflow-y: auto; padding: 4px;
|
|
3514
|
+
background: var(--token-card-strong);
|
|
3515
|
+
border: 1px solid var(--token-outline);
|
|
3516
|
+
border-radius: var(--radius-lg);
|
|
3517
|
+
box-shadow: var(--token-shadow-overlay);
|
|
3518
|
+
z-index: var(--z-popover);
|
|
3519
|
+
transform-origin: top center;
|
|
3520
|
+
animation: select-panel-in var(--duration-fast) var(--ease-out);
|
|
3521
|
+
}
|
|
3522
|
+
@keyframes select-panel-in {
|
|
3523
|
+
from { opacity: 0; transform: scale(0.95); }
|
|
3524
|
+
to { opacity: 1; transform: scale(1); }
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
.select-option {
|
|
3528
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
3529
|
+
padding: 6px 10px; font-size: 12px; text-align: left;
|
|
3530
|
+
border: none; background: transparent; color: var(--token-text);
|
|
3531
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
3532
|
+
transition: background-color var(--duration-fast) var(--ease-out),
|
|
3533
|
+
color var(--duration-fast) var(--ease-out);
|
|
3534
|
+
}
|
|
3535
|
+
.select-option.is-focused { background: var(--token-fill-2); }
|
|
3536
|
+
.select-option.is-selected { color: var(--token-accent); }
|
|
3537
|
+
.select-option.is-selected::after { content: '✓'; margin-left: auto; flex-shrink: 0; }
|
|
3538
|
+
.select-option.is-disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3539
|
+
|
|
3540
|
+
|
|
3541
|
+
/* ── components/sidebar.css ── */
|
|
3542
|
+
/*
|
|
3543
|
+
* @icen.ai/ui — Component: sidebar(侧栏导航,分组折叠配套 behaviors/sidebar)
|
|
3544
|
+
* 用法:<aside class="sidebar sidebar--sticky">
|
|
3545
|
+
* <div class="sidebar-group is-open">
|
|
3546
|
+
* <button class="sidebar-group-title" aria-expanded="true">分组</button>
|
|
3547
|
+
* <div class="sidebar-group-panel"><div class="sidebar-group-panel-inner">
|
|
3548
|
+
* <a class="sidebar-item is-active" href="…">条目</a>
|
|
3549
|
+
* </div></div>
|
|
3550
|
+
* </div>
|
|
3551
|
+
* </aside>
|
|
3552
|
+
* 契约:.is-open 由 behaviors/sidebar 切换(同步 aria-expanded);
|
|
3553
|
+
* 展开动画 = grid-template-rows 0fr↔1fr(同 accordion 约定),内层 overflow:hidden。
|
|
3554
|
+
* 滚动条样式归 base.css 全局纪律,这里只声明 overflow-y。
|
|
3555
|
+
*/
|
|
3556
|
+
|
|
3557
|
+
.sidebar { width: 260px; flex-shrink: 0; overflow-y: auto; padding-right: 4px; }
|
|
3558
|
+
.sidebar--sticky { position: sticky; top: 80px; max-height: calc(100vh - 100px); }
|
|
3559
|
+
|
|
3560
|
+
.sidebar-group { margin-bottom: 8px; }
|
|
3561
|
+
.sidebar-group-title {
|
|
3562
|
+
width: 100%; display: flex; align-items: center; justify-content: space-between;
|
|
3563
|
+
padding: 8px 12px; border-radius: var(--radius-md);
|
|
3564
|
+
font-family: inherit; font-size: 11px; font-weight: 600;
|
|
3565
|
+
text-transform: uppercase; letter-spacing: 1px;
|
|
3566
|
+
color: var(--token-text-muted);
|
|
3567
|
+
background: none; border: 0; cursor: pointer;
|
|
3568
|
+
}
|
|
3569
|
+
.sidebar-group-title:hover { background: var(--token-control-hover); color: var(--token-text); }
|
|
3570
|
+
/* chevron:闭合态 rotate(-90deg),展开回正 */
|
|
3571
|
+
.sidebar-group-title::after {
|
|
3572
|
+
content: '▾'; font-size: 12px; line-height: 1; color: var(--token-text-muted);
|
|
3573
|
+
transform: rotate(-90deg);
|
|
3574
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
3575
|
+
}
|
|
3576
|
+
.sidebar-group.is-open .sidebar-group-title::after { transform: none; }
|
|
3577
|
+
|
|
3578
|
+
.sidebar-group-panel {
|
|
3579
|
+
display: grid; grid-template-rows: 0fr;
|
|
3580
|
+
transition: grid-template-rows var(--duration-normal) var(--ease-out);
|
|
3581
|
+
}
|
|
3582
|
+
.sidebar-group.is-open .sidebar-group-panel { grid-template-rows: 1fr; }
|
|
3583
|
+
.sidebar-group-panel-inner { overflow: hidden; min-height: 0; }
|
|
3584
|
+
|
|
3585
|
+
.sidebar-item {
|
|
3586
|
+
width: 100%; display: flex; align-items: center; gap: 8px;
|
|
3587
|
+
padding: 8px 12px; border-radius: var(--radius-lg);
|
|
3588
|
+
font-size: 13px; color: var(--token-text); text-align: left;
|
|
3589
|
+
text-decoration: none; background: none; border: 0; cursor: pointer;
|
|
3590
|
+
}
|
|
3591
|
+
.sidebar-item:hover { background: var(--token-control-hover); text-decoration: none; }
|
|
3592
|
+
.sidebar-item.is-active { background: var(--token-accent-subtle); color: var(--token-accent); font-weight: 500; }
|
|
3593
|
+
.sidebar-item > svg { width: 14px; height: 14px; flex-shrink: 0; color: var(--token-text-muted); }
|
|
3594
|
+
.sidebar-item.is-active > svg { color: var(--token-accent); }
|
|
3595
|
+
.sidebar-item > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3596
|
+
|
|
3597
|
+
|
|
3598
|
+
/* ── components/slider.css ── */
|
|
3599
|
+
/*
|
|
3600
|
+
* @icen.ai/ui — Component: slider(单值 / 双滑块)
|
|
3601
|
+
* 规格源自 opengal primitives/input.tsx 的 Slider 与 advanced-input.tsx 的 DualRangeSlider;
|
|
3602
|
+
* 与 behaviors/slider.ts 配套(透明原生 range 覆盖层驱动 fill/thumb 定位)。
|
|
3603
|
+
*
|
|
3604
|
+
* 契约(单值):
|
|
3605
|
+
* <div class="slider">
|
|
3606
|
+
* <div class="slider-track"></div>
|
|
3607
|
+
* <div class="slider-fill" style="width:40%"></div>
|
|
3608
|
+
* <div class="slider-thumb" style="left:calc(40% - 8px)"></div>
|
|
3609
|
+
* <input class="slider-native" type="range" />
|
|
3610
|
+
* </div>
|
|
3611
|
+
* 双滑块:.slider--dual 内 .slider-range(left/right 定位选中段)
|
|
3612
|
+
* + .slider-thumb--lo / .slider-thumb--hi + 两个 .slider-native[data-thumb=lo|hi]
|
|
3613
|
+
* label 行:.slider-row(label + 当前值,tabular-nums)
|
|
3614
|
+
*/
|
|
3615
|
+
|
|
3616
|
+
.slider { position: relative; height: 20px; width: 100%; }
|
|
3617
|
+
|
|
3618
|
+
.slider-track {
|
|
3619
|
+
position: absolute; left: 0; right: 0; top: 50%; transform: translateY(-50%);
|
|
3620
|
+
height: 6px; border-radius: var(--radius-full);
|
|
3621
|
+
background: color-mix(in srgb, var(--token-line-soft) 50%, transparent);
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
.slider-fill {
|
|
3625
|
+
position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
3626
|
+
height: 6px; border-radius: var(--radius-full);
|
|
3627
|
+
background: var(--token-accent);
|
|
3628
|
+
transition: width var(--duration-normal) var(--ease-out);
|
|
3629
|
+
}
|
|
3630
|
+
|
|
3631
|
+
/* 双滑块选中段:left = lo%、right = (100 - hi)% */
|
|
3632
|
+
.slider-range {
|
|
3633
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
3634
|
+
height: 6px; border-radius: var(--radius-full);
|
|
3635
|
+
background: var(--token-accent);
|
|
3636
|
+
transition: left var(--duration-normal) var(--ease-out),
|
|
3637
|
+
right var(--duration-normal) var(--ease-out);
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
.slider-thumb {
|
|
3641
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
3642
|
+
width: 16px; height: 16px; border-radius: var(--radius-full);
|
|
3643
|
+
background: var(--token-card);
|
|
3644
|
+
border: 2px solid var(--token-accent);
|
|
3645
|
+
box-shadow: var(--token-shadow-card);
|
|
3646
|
+
pointer-events: none;
|
|
3647
|
+
transition: transform var(--duration-fast) var(--ease-out),
|
|
3648
|
+
box-shadow var(--duration-fast) var(--ease-out);
|
|
3649
|
+
}
|
|
3650
|
+
/* 原生 input 透明,焦点环镜像到可见 thumb 上 */
|
|
3651
|
+
.slider:focus-within .slider-thumb {
|
|
3652
|
+
transform: translateY(-50%) scale(1.1);
|
|
3653
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--token-accent) 50%, transparent),
|
|
3654
|
+
0 0 0 3px var(--token-bg);
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
.slider-native {
|
|
3658
|
+
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
3659
|
+
margin: 0; opacity: 0; cursor: pointer; z-index: 1;
|
|
3660
|
+
}
|
|
3661
|
+
.slider-native:disabled { cursor: not-allowed; }
|
|
3662
|
+
|
|
3663
|
+
.slider-row {
|
|
3664
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
3665
|
+
font-size: 12px; color: var(--token-text-muted);
|
|
3666
|
+
font-variant-numeric: tabular-nums;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
|
|
2090
3670
|
/* ── components/stat.css ── */
|
|
2091
3671
|
/*
|
|
2092
3672
|
* @icen.ai/ui — Component: stat(指标卡)
|
|
@@ -2108,6 +3688,168 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2108
3688
|
.stat-label { color: var(--token-text-faint); font-size: 11px; letter-spacing: 1px; margin-top: 2px; }
|
|
2109
3689
|
|
|
2110
3690
|
|
|
3691
|
+
/* ── components/steps.css ── */
|
|
3692
|
+
/*
|
|
3693
|
+
* @icen.ai/ui — Component: steps(步骤条,横向默认 / .steps--vertical 垂直)
|
|
3694
|
+
* 用法:<ol class="steps">
|
|
3695
|
+
* <li class="step step--finish">
|
|
3696
|
+
* <span class="step-line"></span>
|
|
3697
|
+
* <span class="step-dot">✓</span>
|
|
3698
|
+
* <div class="step-body">
|
|
3699
|
+
* <div class="step-title">已完成</div>
|
|
3700
|
+
* <div class="step-desc">描述</div>
|
|
3701
|
+
* </div>
|
|
3702
|
+
* </li>
|
|
3703
|
+
* </ol>
|
|
3704
|
+
* 三态:.step--finish(accent 实底,内容放 ✓)/ .step--process(accent 描边)/ .step--wait(灰)。
|
|
3705
|
+
* 连接线画在「当前步 → 下一步」之间,颜色跟随当前步状态;末步不显示(.step:last-child)。
|
|
3706
|
+
* 垂直版同一 DOM:.step-line 改为绝对左侧竖线,穿过 16px 间隙接到下一颗圆点。
|
|
3707
|
+
*/
|
|
3708
|
+
|
|
3709
|
+
.steps { display: flex; list-style: none; margin: 0; padding: 0; }
|
|
3710
|
+
.step { flex: 1; position: relative; text-align: center; }
|
|
3711
|
+
|
|
3712
|
+
.step-line {
|
|
3713
|
+
position: absolute; top: 16px; left: calc(50% + 20px);
|
|
3714
|
+
width: calc(100% - 40px); height: 1px;
|
|
3715
|
+
background: var(--token-outline-soft);
|
|
3716
|
+
}
|
|
3717
|
+
.step:last-child .step-line { display: none; }
|
|
3718
|
+
.step--finish .step-line { background: color-mix(in srgb, var(--token-accent) 40%, transparent); }
|
|
3719
|
+
|
|
3720
|
+
.step-dot {
|
|
3721
|
+
width: 32px; height: 32px; border-radius: var(--radius-full);
|
|
3722
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3723
|
+
font-size: 12px; font-weight: 600; position: relative; z-index: 1;
|
|
3724
|
+
}
|
|
3725
|
+
.step--finish .step-dot { background: var(--token-accent); color: var(--token-on-accent); }
|
|
3726
|
+
.step--process .step-dot {
|
|
3727
|
+
background: var(--token-accent-subtle); color: var(--token-accent);
|
|
3728
|
+
border: 2px solid var(--token-accent);
|
|
3729
|
+
}
|
|
3730
|
+
.step--wait .step-dot {
|
|
3731
|
+
background: var(--token-control); color: var(--token-text-faint);
|
|
3732
|
+
border: 1px solid var(--token-outline-soft);
|
|
3733
|
+
}
|
|
3734
|
+
|
|
3735
|
+
.step-title { font-size: 13px; font-weight: 500; margin-top: 6px; }
|
|
3736
|
+
.step--wait .step-title { color: var(--token-text-muted); }
|
|
3737
|
+
.step-desc {
|
|
3738
|
+
font-size: 11px; color: var(--token-text-faint);
|
|
3739
|
+
margin-top: 2px; max-width: 160px; margin-inline: auto;
|
|
3740
|
+
}
|
|
3741
|
+
|
|
3742
|
+
/* ── 垂直变体 ── */
|
|
3743
|
+
.steps--vertical { flex-direction: column; gap: 16px; }
|
|
3744
|
+
.steps--vertical .step { flex: none; display: flex; gap: 12px; text-align: left; }
|
|
3745
|
+
.steps--vertical .step-line {
|
|
3746
|
+
top: 36px; bottom: -16px; left: 16px;
|
|
3747
|
+
width: 1px; height: auto;
|
|
3748
|
+
}
|
|
3749
|
+
.steps--vertical .step-dot { flex-shrink: 0; }
|
|
3750
|
+
.steps--vertical .step-body { padding-top: 4px; }
|
|
3751
|
+
.steps--vertical .step-title { margin-top: 0; }
|
|
3752
|
+
.steps--vertical .step-desc { max-width: none; margin-inline: 0; }
|
|
3753
|
+
|
|
3754
|
+
|
|
3755
|
+
/* ── components/switch.css ── */
|
|
3756
|
+
/*
|
|
3757
|
+
* @icen.ai/ui — Component: switch(开关 / 勾选框 / 单选行)
|
|
3758
|
+
* 纯 CSS 状态组件:状态全走 aria-checked / .is-selected,交互由使用方(或演示页脚本)切换。
|
|
3759
|
+
*
|
|
3760
|
+
* 开关:<button class="switch [--sm]" role="switch" aria-checked="true"><span class="switch-thumb"></span></button>
|
|
3761
|
+
* 勾选:<button class="checkbox focus-ring" role="checkbox" aria-checked="true|false|mixed"></button>
|
|
3762
|
+
* 单选:<div class="radio-group" role="radiogroup">
|
|
3763
|
+
* <button class="radio-item motion is-selected"><span class="radio-dot"></span>…</button>
|
|
3764
|
+
* </div>
|
|
3765
|
+
*/
|
|
3766
|
+
|
|
3767
|
+
/* ── 开关 ── */
|
|
3768
|
+
.switch {
|
|
3769
|
+
position: relative; display: inline-flex; flex-shrink: 0;
|
|
3770
|
+
width: 44px; height: 24px; padding: 0;
|
|
3771
|
+
border: none; border-radius: var(--radius-full);
|
|
3772
|
+
background: var(--token-control-hover); cursor: pointer;
|
|
3773
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
3774
|
+
}
|
|
3775
|
+
.switch[aria-checked='true'] { background: var(--token-accent); }
|
|
3776
|
+
.switch:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3777
|
+
|
|
3778
|
+
.switch-thumb {
|
|
3779
|
+
position: absolute; top: 2px; left: 2px;
|
|
3780
|
+
width: 20px; height: 20px; border-radius: var(--radius-full);
|
|
3781
|
+
background: var(--token-on-accent, #fff);
|
|
3782
|
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
3783
|
+
transition: transform var(--duration-normal) var(--ease-out);
|
|
3784
|
+
}
|
|
3785
|
+
.switch[aria-checked='true'] .switch-thumb { transform: translateX(20px); }
|
|
3786
|
+
|
|
3787
|
+
.switch--sm { width: 36px; height: 20px; }
|
|
3788
|
+
.switch--sm .switch-thumb { width: 16px; height: 16px; }
|
|
3789
|
+
.switch--sm[aria-checked='true'] .switch-thumb { transform: translateX(16px); }
|
|
3790
|
+
|
|
3791
|
+
/* ── 勾选框(✓ 与半选横条均 ::before 自绘,无需内联 svg) ── */
|
|
3792
|
+
.checkbox {
|
|
3793
|
+
display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
|
|
3794
|
+
width: 16px; height: 16px; padding: 0;
|
|
3795
|
+
background: var(--token-control); color: transparent;
|
|
3796
|
+
border: 1px solid var(--token-outline-soft);
|
|
3797
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
3798
|
+
transition: background-color var(--duration-normal) var(--ease-out),
|
|
3799
|
+
border-color var(--duration-normal) var(--ease-out),
|
|
3800
|
+
color var(--duration-normal) var(--ease-out);
|
|
3801
|
+
}
|
|
3802
|
+
.checkbox:hover:not([aria-checked='true']):not([aria-checked='mixed']) {
|
|
3803
|
+
border-color: color-mix(in srgb, var(--token-accent) 50%, transparent);
|
|
3804
|
+
}
|
|
3805
|
+
.checkbox[aria-checked='true'], .checkbox[aria-checked='mixed'] {
|
|
3806
|
+
background: var(--token-accent);
|
|
3807
|
+
border-color: var(--token-accent);
|
|
3808
|
+
color: var(--token-on-accent, #fff);
|
|
3809
|
+
}
|
|
3810
|
+
.checkbox::before {
|
|
3811
|
+
content: ''; width: 10px; height: 10px;
|
|
3812
|
+
background: currentColor; opacity: 0;
|
|
3813
|
+
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
|
|
3814
|
+
}
|
|
3815
|
+
.checkbox[aria-checked='true']::before { opacity: 1; }
|
|
3816
|
+
.checkbox[aria-checked='mixed']::before {
|
|
3817
|
+
opacity: 1; width: 8px; height: 2px;
|
|
3818
|
+
clip-path: none; border-radius: var(--radius-full);
|
|
3819
|
+
}
|
|
3820
|
+
.checkbox:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3821
|
+
|
|
3822
|
+
/* ── 单选行 ── */
|
|
3823
|
+
.radio-group { display: flex; flex-direction: column; gap: 4px; }
|
|
3824
|
+
|
|
3825
|
+
.radio-item {
|
|
3826
|
+
display: flex; align-items: center; gap: 8px; width: 100%;
|
|
3827
|
+
padding: 8px 12px; font-size: 13px; text-align: left;
|
|
3828
|
+
border: none; background: transparent; color: var(--token-text);
|
|
3829
|
+
border-radius: var(--radius-sm); cursor: pointer;
|
|
3830
|
+
}
|
|
3831
|
+
.radio-item.is-selected {
|
|
3832
|
+
background: var(--token-accent-subtle); color: var(--token-accent); font-weight: 500;
|
|
3833
|
+
}
|
|
3834
|
+
.radio-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
3835
|
+
|
|
3836
|
+
.radio-dot {
|
|
3837
|
+
position: relative; width: 16px; height: 16px; flex-shrink: 0;
|
|
3838
|
+
border: 1px solid var(--token-outline-soft);
|
|
3839
|
+
border-radius: var(--radius-full);
|
|
3840
|
+
transition: border-color var(--duration-normal) var(--ease-out);
|
|
3841
|
+
}
|
|
3842
|
+
.radio-item.is-selected .radio-dot { border-color: var(--token-accent); }
|
|
3843
|
+
.radio-dot::after {
|
|
3844
|
+
content: ''; position: absolute; top: 50%; left: 50%;
|
|
3845
|
+
width: 8px; height: 8px; border-radius: var(--radius-full);
|
|
3846
|
+
background: var(--token-accent);
|
|
3847
|
+
transform: translate(-50%, -50%) scale(0);
|
|
3848
|
+
transition: transform var(--duration-fast) var(--ease-out);
|
|
3849
|
+
}
|
|
3850
|
+
.radio-item.is-selected .radio-dot::after { transform: translate(-50%, -50%) scale(1); }
|
|
3851
|
+
|
|
3852
|
+
|
|
2111
3853
|
/* ── components/table.css ── */
|
|
2112
3854
|
/*
|
|
2113
3855
|
* @icen.ai/ui — Component: table(数据表)
|
|
@@ -2159,6 +3901,57 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2159
3901
|
.admin-tabs a.active { color: var(--token-accent); border-bottom-color: var(--token-accent); }
|
|
2160
3902
|
|
|
2161
3903
|
|
|
3904
|
+
/* ── components/tag-input.css ── */
|
|
3905
|
+
/*
|
|
3906
|
+
* @icen.ai/ui — Component: tag-input(多值标签编辑)
|
|
3907
|
+
* 规格源自 opengal primitives/advanced-input.tsx 的 TagInput;与 behaviors/tag-input.ts 配套。
|
|
3908
|
+
*
|
|
3909
|
+
* 契约:
|
|
3910
|
+
* <div class="tag-input control" data-tags="初始,标签" data-max="5">
|
|
3911
|
+
* <input class="tag-input-field" placeholder="…" />
|
|
3912
|
+
* </div>
|
|
3913
|
+
* behavior 按 data-tags 渲染 .tag-chip(含 .tag-chip-x 删除钮),并回写 data-tags;
|
|
3914
|
+
* 达到 data-max 时容器加 .is-max(warning 色边框)并拒加。
|
|
3915
|
+
*/
|
|
3916
|
+
|
|
3917
|
+
.tag-input {
|
|
3918
|
+
display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
3919
|
+
padding: 6px; min-height: 36px;
|
|
3920
|
+
background: var(--token-control);
|
|
3921
|
+
border: 1px solid var(--token-outline-soft);
|
|
3922
|
+
border-radius: var(--radius-lg);
|
|
3923
|
+
}
|
|
3924
|
+
.tag-input:focus-within { border-color: var(--token-accent); }
|
|
3925
|
+
.tag-input.is-max, .tag-input.is-max:focus-within {
|
|
3926
|
+
border-color: color-mix(in srgb, var(--token-warning) 60%, transparent);
|
|
3927
|
+
}
|
|
3928
|
+
.tag-input.is-disabled { opacity: 0.4; }
|
|
3929
|
+
.tag-input.is-disabled .tag-input-field { cursor: not-allowed; }
|
|
3930
|
+
|
|
3931
|
+
.tag-chip {
|
|
3932
|
+
display: inline-flex; align-items: center; gap: 4px;
|
|
3933
|
+
padding: 2px 4px 2px 8px;
|
|
3934
|
+
border-radius: var(--radius-full);
|
|
3935
|
+
background: var(--token-accent-subtle); color: var(--token-accent);
|
|
3936
|
+
font-size: 11px; font-weight: 500;
|
|
3937
|
+
}
|
|
3938
|
+
|
|
3939
|
+
.tag-chip-x {
|
|
3940
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3941
|
+
padding: 2px; border: none; background: transparent; cursor: pointer;
|
|
3942
|
+
border-radius: var(--radius-full); color: inherit; font-size: 11px; line-height: 1;
|
|
3943
|
+
transition: background-color var(--duration-normal) var(--ease-out);
|
|
3944
|
+
}
|
|
3945
|
+
.tag-chip-x:hover { background: var(--token-fill-2); }
|
|
3946
|
+
|
|
3947
|
+
.tag-input-field {
|
|
3948
|
+
flex: 1; min-width: 80px; padding: 4px;
|
|
3949
|
+
background: transparent; border: none; outline: none;
|
|
3950
|
+
color: var(--token-text); font: inherit; font-size: 12px;
|
|
3951
|
+
}
|
|
3952
|
+
.tag-input-field::placeholder { color: color-mix(in srgb, var(--token-text-faint) 60%, transparent); }
|
|
3953
|
+
|
|
3954
|
+
|
|
2162
3955
|
/* ── components/toast.css ── */
|
|
2163
3956
|
/*
|
|
2164
3957
|
* @icen.ai/ui — Component: toast(轻提示,与 behaviors/toast 配套)
|
|
@@ -2166,7 +3959,7 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2166
3959
|
*/
|
|
2167
3960
|
|
|
2168
3961
|
.toast-container {
|
|
2169
|
-
position: fixed; right: 20px; bottom: 20px; z-index:
|
|
3962
|
+
position: fixed; right: 20px; bottom: 20px; z-index: var(--z-toast);
|
|
2170
3963
|
display: flex; flex-direction: column; gap: 10px; align-items: flex-end;
|
|
2171
3964
|
pointer-events: none;
|
|
2172
3965
|
}
|
|
@@ -2188,3 +3981,108 @@ html { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--token-te
|
|
|
2188
3981
|
to { opacity: 1; transform: translateY(0); }
|
|
2189
3982
|
}
|
|
2190
3983
|
|
|
3984
|
+
|
|
3985
|
+
/* ── components/tooltip.css ── */
|
|
3986
|
+
/*
|
|
3987
|
+
* @icen.ai/ui — Component: tooltip(纯 CSS 气泡提示,无 JS)
|
|
3988
|
+
* 契约:任意元素加 data-tooltip="文案";默认在上方,
|
|
3989
|
+
* data-tooltip-side="top|bottom|left|right" 换方向。
|
|
3990
|
+
* hover 延迟 400ms 出现,focus-visible 立即出现。
|
|
3991
|
+
*/
|
|
3992
|
+
|
|
3993
|
+
[data-tooltip] { position: relative; }
|
|
3994
|
+
|
|
3995
|
+
[data-tooltip]::after {
|
|
3996
|
+
content: attr(data-tooltip);
|
|
3997
|
+
position: absolute; z-index: var(--z-tooltip);
|
|
3998
|
+
width: max-content; max-width: min(260px, calc(100vw - 32px));
|
|
3999
|
+
padding: 4px 8px;
|
|
4000
|
+
font-size: 11px; font-weight: 400; line-height: 1.375; white-space: normal; text-align: left;
|
|
4001
|
+
background: var(--token-card-strong); color: var(--token-text);
|
|
4002
|
+
border: 1px solid var(--token-outline-soft);
|
|
4003
|
+
box-shadow: var(--token-shadow-overlay);
|
|
4004
|
+
border-radius: var(--radius-md);
|
|
4005
|
+
opacity: 0; pointer-events: none;
|
|
4006
|
+
transition: opacity 150ms var(--ease-out), transform 150ms var(--ease-out);
|
|
4007
|
+
transition-delay: 400ms;
|
|
4008
|
+
|
|
4009
|
+
/* 默认:上方 */
|
|
4010
|
+
bottom: calc(100% + 4px); left: 50%;
|
|
4011
|
+
transform: translate(-50%, 4px);
|
|
4012
|
+
}
|
|
4013
|
+
|
|
4014
|
+
[data-tooltip-side='bottom']::after {
|
|
4015
|
+
bottom: auto; top: calc(100% + 4px); left: 50%;
|
|
4016
|
+
transform: translate(-50%, -4px);
|
|
4017
|
+
}
|
|
4018
|
+
[data-tooltip-side='left']::after {
|
|
4019
|
+
bottom: auto; top: 50%; left: auto; right: calc(100% + 4px);
|
|
4020
|
+
transform: translate(4px, -50%);
|
|
4021
|
+
}
|
|
4022
|
+
[data-tooltip-side='right']::after {
|
|
4023
|
+
bottom: auto; top: 50%; right: auto; left: calc(100% + 4px);
|
|
4024
|
+
transform: translate(-4px, -50%);
|
|
4025
|
+
}
|
|
4026
|
+
|
|
4027
|
+
[data-tooltip]:hover::after, [data-tooltip]:focus-visible::after {
|
|
4028
|
+
opacity: 1; transform: translate(-50%, 0);
|
|
4029
|
+
}
|
|
4030
|
+
[data-tooltip-side='bottom']:hover::after, [data-tooltip-side='bottom']:focus-visible::after {
|
|
4031
|
+
opacity: 1; transform: translate(-50%, 0);
|
|
4032
|
+
}
|
|
4033
|
+
[data-tooltip-side='left']:hover::after, [data-tooltip-side='left']:focus-visible::after {
|
|
4034
|
+
opacity: 1; transform: translate(0, -50%);
|
|
4035
|
+
}
|
|
4036
|
+
[data-tooltip-side='right']:hover::after, [data-tooltip-side='right']:focus-visible::after {
|
|
4037
|
+
opacity: 1; transform: translate(0, -50%);
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
/* 键盘聚焦立即出现(覆盖 400ms 延迟) */
|
|
4041
|
+
[data-tooltip]:focus-visible::after { transition-delay: 0ms; }
|
|
4042
|
+
|
|
4043
|
+
|
|
4044
|
+
/* ── components/upload.css ── */
|
|
4045
|
+
/*
|
|
4046
|
+
* @icen.ai/ui — Component: upload(拖放上传区)
|
|
4047
|
+
* 规格源自 opengal primitives/advanced-input.tsx 的 Upload;与 behaviors/upload.ts 配套。
|
|
4048
|
+
*
|
|
4049
|
+
* 契约:
|
|
4050
|
+
* <div class="upload control lift focus-ring" role="button" tabindex="0">
|
|
4051
|
+
* <input type="file" multiple />
|
|
4052
|
+
* <div class="upload-icon"><svg …></svg></div>
|
|
4053
|
+
* <p class="upload-title">…</p>
|
|
4054
|
+
* <p class="upload-desc">…</p>
|
|
4055
|
+
* <p class="upload-error" hidden>…</p>
|
|
4056
|
+
* </div>
|
|
4057
|
+
* 拖入时 behavior 加 .is-drag。
|
|
4058
|
+
*/
|
|
4059
|
+
|
|
4060
|
+
.upload {
|
|
4061
|
+
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
4062
|
+
text-align: center; padding: 32px 16px;
|
|
4063
|
+
border: 2px dashed var(--token-outline-soft);
|
|
4064
|
+
border-radius: var(--radius-lg);
|
|
4065
|
+
cursor: pointer;
|
|
4066
|
+
}
|
|
4067
|
+
.upload:hover {
|
|
4068
|
+
border-color: color-mix(in srgb, var(--token-accent) 60%, transparent);
|
|
4069
|
+
background: var(--token-control-hover);
|
|
4070
|
+
}
|
|
4071
|
+
.upload.is-drag {
|
|
4072
|
+
border-color: var(--token-accent);
|
|
4073
|
+
background: var(--token-accent-subtle);
|
|
4074
|
+
}
|
|
4075
|
+
|
|
4076
|
+
.upload input[type='file'] { display: none; }
|
|
4077
|
+
|
|
4078
|
+
.upload-icon {
|
|
4079
|
+
display: flex; align-items: center; justify-content: center;
|
|
4080
|
+
width: 40px; height: 40px; margin-bottom: 8px;
|
|
4081
|
+
border-radius: var(--radius-lg);
|
|
4082
|
+
background: var(--token-accent-subtle); color: var(--token-accent);
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
.upload-title { margin: 0; font-size: 13px; font-weight: 500; color: var(--token-text); }
|
|
4086
|
+
.upload-desc { margin: 2px 0 0; font-size: 11px; color: var(--token-text-faint); }
|
|
4087
|
+
.upload-error { margin: 4px 0 0; font-size: 11px; color: var(--token-error); }
|
|
4088
|
+
|