weifuwu 0.58.0 → 0.59.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 +113 -11
- package/dist/components/Avatar/Avatar.d.ts +2 -0
- package/dist/components/Card/Card.d.ts +4 -0
- package/dist/components/Input/Input.d.ts +2 -0
- package/dist/components/SegmentedControl/SegmentedControl.d.ts +22 -0
- package/dist/components/StatCard/StatCard.d.ts +2 -0
- package/dist/components/Textarea/Textarea.d.ts +4 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +47 -9
- package/dist/components/style.css +807 -116
- package/dist/layout/weifuwu-layout.css +636 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -211,8 +211,8 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
211
211
|
| 资源 | CDN 地址 | 说明 |
|
|
212
212
|
|------|---------|------|
|
|
213
213
|
| `weifuwu/client` | `https://unpkg.com/weifuwu@latest/dist/client/index.js` | 客户端核心(createApp, h, 路由, 状态管理等) |
|
|
214
|
-
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` |
|
|
215
|
-
| 组件样式 | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS +
|
|
214
|
+
| `weifuwu/components` | `https://unpkg.com/weifuwu@latest/dist/components/index.js` | 44 个 UI 组件(Button, Card, Table, Modal 等) |
|
|
215
|
+
| 组件样式 | `https://unpkg.com/weifuwu@latest/dist/components/style.css` | 组件 CSS + 115 个主题 Token + 67 个布局原语 |
|
|
216
216
|
| 独立布局系统 | `https://unpkg.com/weifuwu@latest/dist/layout/weifuwu-layout.css` | 仅 CSS 布局,不依赖 JS |
|
|
217
217
|
|
|
218
218
|
|
|
@@ -250,8 +250,8 @@ createApp().use(router({ routes })).mount('#root', RouteView, { hydrate: true })
|
|
|
250
250
|
| `weifuwu/client` | **ErrorBoundary** | 错误边界组件 | createApp |
|
|
251
251
|
| `weifuwu/client` | **lockScroll/trapFocus** | 滚动锁定 / 焦点陷阱工具 | — |
|
|
252
252
|
| `weifuwu/client` | **popup** | 弹层 fixed 定位工具(`computeFixedPos` / `computeFixedPosRect`) | — |
|
|
253
|
-
| `weifuwu/components` | **
|
|
254
|
-
| `weifuwu/layout` | **CSS 布局** |
|
|
253
|
+
| `weifuwu/components` | **44 个组件** | Button/Table/Modal/Confirm/Toast/... + `confirm()` / `toast()` 命令式中间件 | weifuwu/client |
|
|
254
|
+
| `weifuwu/layout` | **CSS 布局** | 67 个布局原语 + 115 个主题 Token(也支持 `weifuwu/layout/style.css`) | — |
|
|
255
255
|
|
|
256
256
|
---
|
|
257
257
|
|
|
@@ -2148,11 +2148,11 @@ import type { RouterOptions } from 'weifuwu/client'
|
|
|
2148
2148
|
|
|
2149
2149
|
# 组件库 (`weifuwu/components`)
|
|
2150
2150
|
|
|
2151
|
-
|
|
2151
|
+
44 个 HTML 原语组件。每个是 `(_init, ctx) => (props) => VNode`(两阶段组件,与前端框架同一模型),引用 `--wf-*` CSS 变量做主题。另含 `confirm()` / `toast()` 命令式中间件。
|
|
2152
2152
|
|
|
2153
2153
|
```ts
|
|
2154
2154
|
import { Button, Input, Table, Modal, Toast } from 'weifuwu/components'
|
|
2155
|
-
import 'weifuwu/components/style.css' // 包含 Token +
|
|
2155
|
+
import 'weifuwu/components/style.css' // 包含 Token + 67 布局原语 + 组件样式,一次性引入
|
|
2156
2156
|
```
|
|
2157
2157
|
|
|
2158
2158
|
### 使用示例
|
|
@@ -2381,7 +2381,9 @@ props 变化 ──────────────────────
|
|
|
2381
2381
|
|
|
2382
2382
|
# 布局系统 (`weifuwu/layout`)
|
|
2383
2383
|
|
|
2384
|
-
纯 CSS 布局原语 +
|
|
2384
|
+
纯 CSS 布局原语 + 工具类 + 115 个主题 Token。不绑定任何 JS 框架。
|
|
2385
|
+
|
|
2386
|
+
> **学习路径与命名规范**:见 [`docs/style-guide.md`](./docs/style-guide.md)——统一语法 `wf-<域>-<名>`、三档学习(组件 → 10 核心原语 → 完整速查)、场景速查、变量定制。
|
|
2385
2387
|
|
|
2386
2388
|
> **全栈 weifuwu 项目**:`weifuwu/components/style.css` 已包含布局系统,一条 import 就够了,无需单独引用本页。
|
|
2387
2389
|
> 本页仅适用于**非 weifuwu 项目**或**只需 CSS 布局**的场景。
|
|
@@ -2403,7 +2405,7 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2403
2405
|
也支持相对路径:`ctx.ui.css('./src/style.css')`。
|
|
2404
2406
|
```
|
|
2405
2407
|
|
|
2406
|
-
##
|
|
2408
|
+
## 67 个布局原语
|
|
2407
2409
|
|
|
2408
2410
|
| 类别 | 原语 | 效果 |
|
|
2409
2411
|
|------|------|------|
|
|
@@ -2440,13 +2442,56 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2440
2442
|
| | `wf-inline` | display: inline |
|
|
2441
2443
|
| | `wf-inline-block` | display: inline-block |
|
|
2442
2444
|
| | `wf-contents` | display: contents |
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
+
| **间距** | `wf-p-*` / `wf-px-*` / `wf-py-*`(xs~2xl) | padding:全/水平/垂直,引用 `--wf-space-*` |
|
|
2446
|
+
| | `wf-mt-*` / `wf-mb-*` / `wf-my-*`(xs~2xl) | margin:top/bottom/垂直 |
|
|
2447
|
+
| | `wf-mx-auto` / `wf-my-auto` | margin: auto 居中 |
|
|
2448
|
+
| | `wf-gap-*`(xs~2xl) | 为 flex/grid 原语设置 `--wf-gap` |
|
|
2449
|
+
| **尺寸** | `wf-w-full` / `wf-h-full` / `wf-w-auto` | 宽/高 100%、auto |
|
|
2450
|
+
| **边框** | `wf-border` / `wf-border-t/b/l/r` | 1px 边框(`--wf-border-width` + `--wf-color-border`) |
|
|
2451
|
+
| **面工具** | `wf-bg-secondary/tertiary/brand/success/warning/error/info` | 语义背景色(`--wf-color-*-bg`) |
|
|
2452
|
+
| | `wf-pill` | 胶囊圆角(999px,状态徽章/标签/色块) |
|
|
2453
|
+
| | `wf-rounded-sm` `wf-rounded` `wf-rounded-md` `wf-rounded-lg` | 圆角工具(`--wf-radius-*`) |
|
|
2454
|
+
| **气泡** | `wf-bubble` / `wf-bubble--own` / `wf-bubble--ai` | 聊天气泡(pre-wrap + 折行内建) |
|
|
2455
|
+
| **打印** | `wf-print-hidden` / `wf-print-block` | 导出 PDF 时隐藏工具区 / 恢复块级 |
|
|
2456
|
+
| **行高** | `wf-leading-tight` `wf-leading-base` `wf-leading-relaxed` | line-height(`--wf-line-height-*`) |
|
|
2457
|
+
| **指针** | `wf-pointer` / `wf-not-allowed` | cursor: pointer / not-allowed |
|
|
2458
|
+
| **内容排版** | `wf-prose` | 富文本正文(文章/博客/文档,一个类包 h2/p/ul/blockquote/pre…) |
|
|
2459
|
+
| **外壳** | `wf-app-shell` | 应用外壳:侧边栏 + 主区 grid(`--wf-sidebar-width`) |
|
|
2460
|
+
| | `wf-sidebar` `wf-sidebar-header` `wf-sidebar-body` `wf-sidebar-footer` | 侧边栏:品牌区/导航区/底部用户区,sticky 全高 |
|
|
2461
|
+
| | `wf-nav` `wf-nav-group` `wf-nav-item` `wf-nav-icon` | 导航:分组标题 + 链接项(`--active` 激活态) |
|
|
2462
|
+
| | `wf-main` | 主内容区(padding + min-width: 0) |
|
|
2463
|
+
| | `wf-text-*` 排版工具 | 见下文「排版工具」 |
|
|
2464
|
+
|
|
2465
|
+
### 排版工具(`wf-text-*`)
|
|
2466
|
+
|
|
2467
|
+
| 工具 | 效果 |
|
|
2468
|
+
|------|------|
|
|
2469
|
+
| `wf-text-left/center/right` | text-align |
|
|
2470
|
+
| `wf-text-xs…5xl` | 字号(`--wf-font-size-*`) |
|
|
2471
|
+
| `wf-text-secondary/tertiary/disabled/brand` | 中性色阶 |
|
|
2472
|
+
| `wf-text-success/warning/error/info` | 语义色文本(`--wf-color-*`) |
|
|
2473
|
+
| `wf-text-medium/semibold/bold` | 字重 |
|
|
2474
|
+
| `wf-tracking-normal/wide/wider` | letter-spacing |
|
|
2475
|
+
| `wf-uppercase/lowercase/capitalize` | text-transform |
|
|
2476
|
+
| `wf-pre-wrap` | white-space: pre-wrap + word-break(聊天气泡/代码) |
|
|
2477
|
+
| `wf-break-word` | overflow-wrap + word-break |
|
|
2478
|
+
| `wf-text-nowrap` | white-space: nowrap |
|
|
2479
|
+
| `wf-truncate` | 单行省略(ellipsis) |
|
|
2480
|
+
| `wf-line-clamp-2/3` | 多行截断 |
|
|
2481
|
+
|
|
2482
|
+
## 115 个主题 Token
|
|
2483
|
+
|
|
2484
|
+
**双层结构**:原始层(Primitive,色值只定义一次,品牌/暗色调校改这里)+ 语义层(Semantic,组件消费)。
|
|
2445
2485
|
|
|
2446
2486
|
```css
|
|
2487
|
+
/* ── 原始层 — 品牌/中性色值 + 暗色值,主题定制改这一层 ── */
|
|
2488
|
+
--wf-brand-500 / --wf-brand-600 / --wf-brand-50 /* 品牌主色/悬停/浅底 */
|
|
2489
|
+
--wf-slate-900…50 / --wf-white /* 中性阶 */
|
|
2490
|
+
--wf-dark-* /* 暗色值(暗色模式经间接层引用,零硬编码) */
|
|
2491
|
+
|
|
2492
|
+
/* ── 语义层 — 组件消费,暗色/主题切换覆盖这里 ── */
|
|
2447
2493
|
/* 品牌色 */
|
|
2448
2494
|
--wf-color-primary / --wf-color-primary-hover / --wf-color-primary-bg
|
|
2449
|
-
--wf-color-secondary / --wf-color-secondary-bg
|
|
2450
2495
|
|
|
2451
2496
|
/* 语义色 */
|
|
2452
2497
|
--wf-color-success / --wf-color-success-bg
|
|
@@ -2496,6 +2541,9 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2496
2541
|
--wf-accent-color / --wf-caret-color
|
|
2497
2542
|
--wf-opacity-disabled / --wf-opacity-overlay
|
|
2498
2543
|
--wf-pop-z / --wf-cover-z
|
|
2544
|
+
|
|
2545
|
+
/* 应用外壳 */
|
|
2546
|
+
--wf-sidebar-width
|
|
2499
2547
|
```
|
|
2500
2548
|
|
|
2501
2549
|
### 暗色模式
|
|
@@ -2511,10 +2559,64 @@ app.get('/layout.css', (req, ctx) => ctx.ui.css('weifuwu/layout'))
|
|
|
2511
2559
|
// 系统为暗色时自动生效;加 data-theme="light" 可强制亮色
|
|
2512
2560
|
```
|
|
2513
2561
|
|
|
2562
|
+
暗色值定义在原始层 `--wf-dark-*`(只写一次),`_dark.css` 两段仅做语义映射——改暗色调校只动原始层,无硬编码色值。
|
|
2563
|
+
|
|
2514
2564
|
---
|
|
2515
2565
|
|
|
2516
2566
|
# 样式定制指南
|
|
2517
2567
|
|
|
2568
|
+
## 组件定制钩子(零覆盖 CSS)
|
|
2569
|
+
|
|
2570
|
+
关键组件视觉全部变量化——定制只需设一个变量(默认值 = 现有 token):
|
|
2571
|
+
|
|
2572
|
+
```html
|
|
2573
|
+
<style>
|
|
2574
|
+
:root {
|
|
2575
|
+
--wf-brand-500: #7c3aed; /* 品牌换色:改原始层一个值,全站跟随 */
|
|
2576
|
+
--wf-dark-brand-500: #a78bfa; /* 暗色品牌(可选) */
|
|
2577
|
+
--wf-modal-width: 640px; /* 组件定制:设一个变量 */
|
|
2578
|
+
--wf-btn-radius: 999px;
|
|
2579
|
+
--wf-field-height: 44px;
|
|
2580
|
+
--wf-card-shadow: 0 8px 24px rgba(0,0,0,.12);
|
|
2581
|
+
}
|
|
2582
|
+
</style>
|
|
2583
|
+
```
|
|
2584
|
+
|
|
2585
|
+
钩子清单:`--wf-btn-*` `--wf-card-*` `--wf-field-*` `--wf-modal-*` `--wf-drawer-width` `--wf-toast-*` `--wf-alert-radius` `--wf-badge-radius` `--wf-tag-radius` `--wf-switch-radius` `--wf-popover-*` `--wf-tooltip-radius` `--wf-dropdown-min-width` `--wf-datepicker-*`。
|
|
2586
|
+
|
|
2587
|
+
**覆盖优先级(@layer)**:`@layer tokens, base, layout, utilities, components`——你写的未分层 CSS 天然最高优先级;也可用 `@layer utilities` 精准覆盖我们。
|
|
2588
|
+
|
|
2589
|
+
## 零自定义 CSS 模式(推荐)
|
|
2590
|
+
|
|
2591
|
+
一个项目只需要引用**一个 CSS 文件**(`weifuwu/components/style.css`,内含 Token + 布局原语 + 组件样式),
|
|
2592
|
+
业务代码全部由组件 + `wf-*` 原语承担,**不需要再写 `style.css`**:
|
|
2593
|
+
|
|
2594
|
+
```tsx
|
|
2595
|
+
// 组件 → 页面功能块;wf-* 原语 → 块之间的空间关系;--wf-* → 业务色值
|
|
2596
|
+
<PageHeader title="仪表盘" sub="欢迎回来">
|
|
2597
|
+
<Button variant="primary">+ 新建</Button>
|
|
2598
|
+
</PageHeader>
|
|
2599
|
+
<div class="wf-row wf-gap-lg">
|
|
2600
|
+
<StatCard label="总用户" value="1,234" trend="up" trendLabel="12%" />
|
|
2601
|
+
</div>
|
|
2602
|
+
```
|
|
2603
|
+
|
|
2604
|
+
主题定制(品牌色/圆角/字体)不需要独立文件——**内联在 HTML 的 `<style>` 里即可**:
|
|
2605
|
+
|
|
2606
|
+
```html
|
|
2607
|
+
<style>
|
|
2608
|
+
:root {
|
|
2609
|
+
--wf-color-primary: #6366f1;
|
|
2610
|
+
--wf-radius: 8px;
|
|
2611
|
+
}
|
|
2612
|
+
</style>
|
|
2613
|
+
```
|
|
2614
|
+
|
|
2615
|
+
完整的零样式示例:`apps/weifuwu-demo`(一个页面 = 组件 + 原语,无 style.css)。
|
|
2616
|
+
|
|
2617
|
+
诚实例外(合理场景,仍可内联 `<style>` 解决):打印/PDF 导出规则、第三方库宿主样式、
|
|
2618
|
+
业务特有的一次性视觉(如色板选择器交互)。
|
|
2619
|
+
|
|
2518
2620
|
## 全局主题变量
|
|
2519
2621
|
|
|
2520
2622
|
所有组件引用 `--wf-*` CSS 变量。在根元素覆盖即可定制主题:
|
|
@@ -3,6 +3,10 @@ export interface CardProps {
|
|
|
3
3
|
variant?: 'default' | 'outlined';
|
|
4
4
|
padding?: 'sm' | 'md' | 'lg';
|
|
5
5
|
clickable?: boolean;
|
|
6
|
+
/** hover 抬升(阴影 + 上移),适合可点击/可悬停的卡片 */
|
|
7
|
+
hover?: boolean;
|
|
8
|
+
/** 选中态(边框高亮 + 品牌浅底),适合选择卡片 */
|
|
9
|
+
active?: boolean;
|
|
6
10
|
onClick?: () => void;
|
|
7
11
|
children?: any;
|
|
8
12
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Component } from '../../client/vnode.ts';
|
|
2
|
+
export interface SegmentedOption {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface SegmentedControlProps {
|
|
8
|
+
/** 选项列表(label 可为字符串或任意 VNode) */
|
|
9
|
+
options: SegmentedOption[];
|
|
10
|
+
/** 当前选中值 */
|
|
11
|
+
value?: string;
|
|
12
|
+
onChange?: (value: string) => void;
|
|
13
|
+
size?: 'sm' | 'md';
|
|
14
|
+
/** 撑满父容器宽度(选项等分) */
|
|
15
|
+
block?: boolean;
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 分段控件 — 单选切换(模式切换 / 状态筛选 / 模板选择)
|
|
20
|
+
* 语义:toggle group(aria-pressed),键盘 focus-visible 可见
|
|
21
|
+
*/
|
|
22
|
+
export declare const SegmentedControl: Component<SegmentedControlProps>;
|
|
@@ -8,6 +8,10 @@ export interface TextareaProps {
|
|
|
8
8
|
error?: string;
|
|
9
9
|
hint?: string;
|
|
10
10
|
rows?: number;
|
|
11
|
+
/** 最大字符数(同时限制输入) */
|
|
12
|
+
maxLength?: number;
|
|
13
|
+
/** 显示字数统计(右下角;配合受控 value 实时更新) */
|
|
14
|
+
showCount?: boolean;
|
|
11
15
|
onInput?: (e: Event) => void;
|
|
12
16
|
}
|
|
13
17
|
export declare const Textarea: Component<TextareaProps>;
|
|
@@ -59,6 +59,8 @@ export { Slider } from './Slider/Slider.ts';
|
|
|
59
59
|
export type { SliderProps } from './Slider/Slider.ts';
|
|
60
60
|
export { SearchInput } from './SearchInput/SearchInput.ts';
|
|
61
61
|
export type { SearchInputProps } from './SearchInput/SearchInput.ts';
|
|
62
|
+
export { SegmentedControl } from './SegmentedControl/SegmentedControl.ts';
|
|
63
|
+
export type { SegmentedControlProps, SegmentedOption } from './SegmentedControl/SegmentedControl.ts';
|
|
62
64
|
export { ProgressBar } from './ProgressBar/ProgressBar.ts';
|
|
63
65
|
export type { ProgressBarProps } from './ProgressBar/ProgressBar.ts';
|
|
64
66
|
export { Accordion } from './Accordion/Accordion.ts';
|
package/dist/components/index.js
CHANGED
|
@@ -52,9 +52,9 @@ var Button = (_init, ctx) => (props) => {
|
|
|
52
52
|
|
|
53
53
|
// src/components/Input/Input.ts
|
|
54
54
|
var Input = (_init) => (props) => {
|
|
55
|
-
const { label, name, type = "text", value, placeholder, required, disabled, error, hint, onInput, onChange } = props;
|
|
55
|
+
const { label, name, type = "text", value, placeholder, required, disabled, error, hint, variant = "default", onInput, onChange } = props;
|
|
56
56
|
const inputEl = h("input", {
|
|
57
|
-
class: "wf-input"
|
|
57
|
+
class: `wf-input${variant === "borderless" ? " wf-input--borderless" : ""}`,
|
|
58
58
|
name: name || void 0,
|
|
59
59
|
type,
|
|
60
60
|
value: value ?? "",
|
|
@@ -79,7 +79,7 @@ var Input = (_init) => (props) => {
|
|
|
79
79
|
|
|
80
80
|
// src/components/Textarea/Textarea.ts
|
|
81
81
|
var Textarea = (_init, _ctx) => (props) => {
|
|
82
|
-
const { label, value, placeholder, required, disabled, error, hint, rows = 3, onInput } = props;
|
|
82
|
+
const { label, value, placeholder, required, disabled, error, hint, rows = 3, maxLength, showCount, onInput } = props;
|
|
83
83
|
const textareaEl = h("textarea", {
|
|
84
84
|
class: "wf-textarea",
|
|
85
85
|
value: value ?? "",
|
|
@@ -87,6 +87,7 @@ var Textarea = (_init, _ctx) => (props) => {
|
|
|
87
87
|
required: required || void 0,
|
|
88
88
|
disabled: disabled || void 0,
|
|
89
89
|
rows,
|
|
90
|
+
maxLength,
|
|
90
91
|
onInput
|
|
91
92
|
});
|
|
92
93
|
const children = [];
|
|
@@ -96,6 +97,14 @@ var Textarea = (_init, _ctx) => (props) => {
|
|
|
96
97
|
children.push(h("label", { class: "wf-textarea-label" }, labelContent));
|
|
97
98
|
}
|
|
98
99
|
children.push(textareaEl);
|
|
100
|
+
if (showCount) {
|
|
101
|
+
const len = (value ?? "").length;
|
|
102
|
+
const over = maxLength != null && len > maxLength;
|
|
103
|
+
children.push(h("div", {
|
|
104
|
+
class: `wf-textarea-count${over ? " wf-textarea-count--over" : ""}`,
|
|
105
|
+
"aria-live": "polite"
|
|
106
|
+
}, maxLength != null ? `${len}/${maxLength}` : String(len)));
|
|
107
|
+
}
|
|
99
108
|
if (error) children.push(h("div", { class: "wf-textarea-err" }, error));
|
|
100
109
|
if (hint && !error) children.push(h("div", { class: "wf-textarea-hint" }, hint));
|
|
101
110
|
return h("div", { class: `wf-textarea-wrap${error ? " wf-textarea--err" : ""}` }, children);
|
|
@@ -1059,12 +1068,14 @@ function getPageRange(current, total) {
|
|
|
1059
1068
|
|
|
1060
1069
|
// src/components/Card/Card.ts
|
|
1061
1070
|
var Card = (_init, _ctx) => (props) => {
|
|
1062
|
-
const { variant = "default", padding = "md", clickable, onClick, children } = props;
|
|
1071
|
+
const { variant = "default", padding = "md", clickable, hover, active, onClick, children } = props;
|
|
1063
1072
|
const cls = [
|
|
1064
1073
|
"wf-card",
|
|
1065
1074
|
`wf-card--${variant}`,
|
|
1066
1075
|
`wf-card--pad-${padding}`,
|
|
1067
|
-
clickable && "wf-card--clickable"
|
|
1076
|
+
clickable && "wf-card--clickable",
|
|
1077
|
+
hover && "wf-card--hover",
|
|
1078
|
+
active && "wf-card--active"
|
|
1068
1079
|
].filter(Boolean).join(" ");
|
|
1069
1080
|
return h("div", { class: cls, onClick, role: clickable ? "button" : void 0, tabindex: clickable ? 0 : void 0 }, children);
|
|
1070
1081
|
};
|
|
@@ -1086,7 +1097,7 @@ function hashColor(name) {
|
|
|
1086
1097
|
return COLORS[Math.abs(hash) % COLORS.length];
|
|
1087
1098
|
}
|
|
1088
1099
|
var Avatar = (_init, _ctx) => (props) => {
|
|
1089
|
-
const { name = "", src, size = "md" } = props;
|
|
1100
|
+
const { name = "", src, size = "md", color } = props;
|
|
1090
1101
|
const initial = name.trim()[0]?.toUpperCase() ?? "?";
|
|
1091
1102
|
if (src) {
|
|
1092
1103
|
return h("img", {
|
|
@@ -1097,7 +1108,7 @@ var Avatar = (_init, _ctx) => (props) => {
|
|
|
1097
1108
|
}
|
|
1098
1109
|
return h("div", {
|
|
1099
1110
|
class: `wf-avatar wf-avatar--${size}`,
|
|
1100
|
-
style: { background: hashColor(name) }
|
|
1111
|
+
style: { background: color ?? hashColor(name) }
|
|
1101
1112
|
}, initial);
|
|
1102
1113
|
};
|
|
1103
1114
|
|
|
@@ -1113,7 +1124,7 @@ var Tag = (_init, _ctx) => (props) => {
|
|
|
1113
1124
|
|
|
1114
1125
|
// src/components/StatCard/StatCard.ts
|
|
1115
1126
|
var StatCard = (_init, _ctx) => (props) => {
|
|
1116
|
-
const { label, value, trend, trendLabel, icon } = props;
|
|
1127
|
+
const { label, value, trend, trendLabel, icon, onClick } = props;
|
|
1117
1128
|
const children = [];
|
|
1118
1129
|
if (icon) children.push(h("div", { class: "wf-stat-icon" }, icon));
|
|
1119
1130
|
children.push(h("div", { class: "wf-stat-value" }, String(value)));
|
|
@@ -1126,7 +1137,12 @@ var StatCard = (_init, _ctx) => (props) => {
|
|
|
1126
1137
|
trendLabel ? h("span", { class: "wf-stat-trend-label" }, trendLabel) : null
|
|
1127
1138
|
].filter(Boolean)));
|
|
1128
1139
|
}
|
|
1129
|
-
return h("div", {
|
|
1140
|
+
return h("div", {
|
|
1141
|
+
class: `wf-stat${onClick ? " wf-stat--clickable" : ""}`,
|
|
1142
|
+
onClick,
|
|
1143
|
+
role: onClick ? "button" : void 0,
|
|
1144
|
+
tabindex: onClick ? 0 : void 0
|
|
1145
|
+
}, children);
|
|
1130
1146
|
};
|
|
1131
1147
|
|
|
1132
1148
|
// src/components/Steps/Steps.ts
|
|
@@ -1279,6 +1295,27 @@ var SearchInput = (_init, _ctx) => (props) => {
|
|
|
1279
1295
|
].filter(Boolean));
|
|
1280
1296
|
};
|
|
1281
1297
|
|
|
1298
|
+
// src/components/SegmentedControl/SegmentedControl.ts
|
|
1299
|
+
var SegmentedControl = (_init, _ctx) => (props) => {
|
|
1300
|
+
const { options, value, onChange, size = "md", block, ariaLabel } = props;
|
|
1301
|
+
const cls = [
|
|
1302
|
+
"wf-segmented",
|
|
1303
|
+
size === "sm" && "wf-segmented--sm",
|
|
1304
|
+
block && "wf-segmented--block"
|
|
1305
|
+
].filter(Boolean).join(" ");
|
|
1306
|
+
return h(
|
|
1307
|
+
"div",
|
|
1308
|
+
{ class: cls, role: "group", "aria-label": ariaLabel },
|
|
1309
|
+
options.map((opt) => h("button", {
|
|
1310
|
+
type: "button",
|
|
1311
|
+
class: `wf-segmented-option${opt.value === value ? " wf-segmented-option--active" : ""}`,
|
|
1312
|
+
"aria-pressed": opt.value === value ? "true" : "false",
|
|
1313
|
+
disabled: opt.disabled || void 0,
|
|
1314
|
+
onClick: opt.disabled ? void 0 : () => onChange?.(opt.value)
|
|
1315
|
+
}, opt.label))
|
|
1316
|
+
);
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1282
1319
|
// src/components/ProgressBar/ProgressBar.ts
|
|
1283
1320
|
var ProgressBar = (_init, ctx) => (props) => {
|
|
1284
1321
|
const { value = 0, max = 100, label, showValue } = props;
|
|
@@ -3200,6 +3237,7 @@ export {
|
|
|
3200
3237
|
ProgressBar,
|
|
3201
3238
|
RadioGroup,
|
|
3202
3239
|
SearchInput,
|
|
3240
|
+
SegmentedControl,
|
|
3203
3241
|
Select,
|
|
3204
3242
|
Skeleton,
|
|
3205
3243
|
Slider,
|