@doubao-dev/template 0.0.42 → 0.1.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 CHANGED
@@ -51,7 +51,7 @@ export default function ProductCard() {
51
51
 
52
52
  ## Primitives
53
53
 
54
- `primitives` 是模板体系中的可组合业务单元,不包含完整卡片外壳。`Title`、`Tag`、`Price`、后续的 `Play` 等单元统一通过该子入口导入。
54
+ `primitives` 是模板体系中的可组合业务单元,不包含完整卡片外壳。`Title`、`Tag`、`Icon`、`OptionGroup`、`Price`、后续的 `Play` 等单元统一通过该子入口导入。
55
55
 
56
56
  ### Title
57
57
 
@@ -96,6 +96,69 @@ import { Tag } from '@doubao-dev/template/primitives';
96
96
  - 标签内容不会收缩或截断;业务容器负责测量或通过换行裁切隐藏放不下的整枚标签,不能只裁切标签文字。
97
97
  - `className` 和 `style` 可用于样式定制。
98
98
 
99
+ ### Icon
100
+
101
+ 图标组件,通过名称选择模板体系提供的图标。
102
+
103
+ ```tsx
104
+ import { Icon } from '@doubao-dev/template/primitives';
105
+
106
+ <Icon name="add" />
107
+ ```
108
+
109
+ 说明:
110
+
111
+ - `name` 当前仅支持 `add`。
112
+ - 默认尺寸为 14px × 14px。
113
+ - `className` 和 `style` 可用于调整图标尺寸和布局;当前 `add` 图标使用 `neutral-50` 并自动适配主题。
114
+
115
+ ### OptionGroup
116
+
117
+ 选项集合组件,用于座席、乘车人等选择场景,统一管理选项布局、换行和选择状态。
118
+
119
+ ```tsx
120
+ import { useState } from '@byted-lynx/react';
121
+ import { OptionGroup } from '@doubao-dev/template/primitives';
122
+
123
+ const [seatValue, setSeatValue] = useState<string | number>('second');
124
+
125
+ <OptionGroup
126
+ size="lg"
127
+ columns={3}
128
+ items={[
129
+ { value: 'second', title: '二等座', subtitle: '¥38.5 有票' },
130
+ { value: 'first', title: '一等座', subtitle: '¥60 有票' },
131
+ { value: 'business', title: '商务座', subtitle: '¥130 16张' },
132
+ { value: 'standing', title: '无座', subtitle: '¥38.5 有票' }
133
+ ]}
134
+ value={seatValue}
135
+ onChange={(nextValue) => setSeatValue(nextValue)}
136
+ />
137
+
138
+ const [passengerValues, setPassengerValues] = useState<Array<string | number>>(['wang']);
139
+
140
+ <OptionGroup
141
+ selectionMode="multiple"
142
+ maxSelected={2}
143
+ items={[
144
+ { value: 'wang', title: '王一山' },
145
+ { value: 'zheng', title: '郑天' },
146
+ { value: 'add', title: '添加', icon: 'add' }
147
+ ]}
148
+ value={passengerValues}
149
+ onChange={(nextValues) => setPassengerValues(nextValues)}
150
+ />
151
+ ```
152
+
153
+ 说明:
154
+
155
+ - `items` 是唯一的选项内容入口,不支持 `children`;每项使用唯一的 `value` 标识,并可配置 `title`、`disabled` 和独立 `onClick`。
156
+ - `size` 支持 `md | lg`,默认是 `md`;`md` 高 44px、由内容撑开并自动换行,支持 `icon`;`lg` 高 62px、按栏等分并自动换行,支持 `subtitle`。
157
+ - `columns` 仅用于 `lg`,默认每行 3 栏,选项间距固定为 8px;最后一行的选项保持单栏宽度。
158
+ - `selectionMode` 默认是 `single`,此时 `value` 和 `onChange` 使用单个值;传 `multiple` 时使用值数组,并可通过 `maxSelected={2}` 限制为双选;达到上限后点击未选项不会触发 `onChange`,但仍会触发该 item 的 `onClick`,已选项仍可取消。
159
+ - 用户点击选项时,组件先计算新选择并触发 `onChange`,再触发该 item 的 `onClick`;禁用项不触发事件。
160
+ - `className` 和 `style` 只作用于集合根节点,单项样式由模板统一管理。
161
+
99
162
  ### Price
100
163
 
101
164
  价格展示组件,用于卡片内容中的金额、币种、前后缀、辅助信息和原价排版。`Price` 是业务展示组件,不包含卡片外壳。
@@ -121,7 +184,7 @@ import { Price } from '@doubao-dev/template/primitives';
121
184
 
122
185
  ## ContentCard
123
186
 
124
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/content-card.png" alt="ContentCard 示例" width="400" />
187
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/content-card.png" alt="ContentCard 示例" width="400" />
125
188
 
126
189
  通用列表卡,由标题栏、列表内容和底部操作区组成,适合商品、服务、内容入口等可重复条目。
127
190
 
@@ -163,7 +226,7 @@ import { ContentCard } from '@doubao-dev/template';
163
226
 
164
227
  ## PriceActionCard
165
228
 
166
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/price-action-card.png" alt="PriceActionCard 示例" width="400" />
229
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/price-action-card.png" alt="PriceActionCard 示例" width="400" />
167
230
 
168
231
  价格操作卡,由多条"价格 + 信息 + 右侧按钮"组成,适合报价、权益、服务方案等结果列表。
169
232
 
@@ -196,7 +259,7 @@ import { PriceActionCard } from '@doubao-dev/template';
196
259
 
197
260
  ## CheckoutCard
198
261
 
199
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/checkout-card.png" alt="CheckoutCard 示例" width="400" />
262
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/checkout-card.png" alt="CheckoutCard 示例" width="400" />
200
263
 
201
264
  通用提单卡,由商品摘要、提单信息、费用汇总和底部操作区组成,适合下单确认、支付确认等场景。
202
265
 
@@ -247,7 +310,7 @@ import { CheckoutCard } from '@doubao-dev/template';
247
310
 
248
311
  ## TicketOrderCard
249
312
 
250
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/ticket-checkout-card.png" alt="TicketOrderCard 示例" width="400" />
313
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/ticket-checkout-card.png" alt="TicketOrderCard 示例" width="400" />
251
314
 
252
315
  > **已废弃:** 请使用 `CheckoutCard`。`TicketOrderCard` 仅为兼容现有代码保留。
253
316
 
@@ -294,7 +357,7 @@ import { TicketOrderCard } from '@doubao-dev/template';
294
357
 
295
358
  ## TransitCard
296
359
 
297
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/transit-card.png" alt="TransitCard 示例" width="400" />
360
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/transit-card.png" alt="TransitCard 示例" width="400" />
298
361
 
299
362
  交通票务列表卡,由多条出发、中转、到达和价格信息组成,适合展示机票、火车票、大巴等推荐列表。
300
363
 
@@ -342,7 +405,7 @@ import { TransitCard } from '@doubao-dev/template';
342
405
 
343
406
  ## AskHumanCard
344
407
 
345
- <img src="https://unpkg.com/@doubao-dev/template@0.0.42/assets/ask-human-card.png" alt="AskHumanCard 示例" width="400" />
408
+ <img src="https://unpkg.com/@doubao-dev/template@0.1.0/assets/ask-human-card.png" alt="AskHumanCard 示例" width="400" />
346
409
 
347
410
  人工确认选项卡,由一组选项组成,适合让用户在多个候选项中确认选择。
348
411
 
@@ -0,0 +1,6 @@
1
+ import type { IconName, IconProps } from './types.js';
2
+ import './styles.scss';
3
+ export type { IconName, IconProps } from './types.js';
4
+ export declare function isIconSupported(name: string): name is IconName;
5
+ export declare function Icon({ name, className, style }: IconProps): any;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { useTheme } from "@byted-doubao-apps/components";
2
+ import { clsx } from "clsx";
3
+ import "./styles.css";
4
+ function getAddIconContent(color) {
5
+ return `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
6
+ <g transform="translate(1.16667 1.10835)">
7
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M5.83333 2.80481e-08C6.18771 4.35387e-08 6.475 0.287284 6.475 0.641667L6.475 11.1417C6.475 11.496 6.18771 11.7833 5.83333 11.7833C5.47895 11.7833 5.19166 11.496 5.19166 11.1417L5.19166 0.641667C5.19166 0.287284 5.47895 1.25576e-08 5.83333 2.80481e-08Z" fill="${color}"/>
8
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M11.7833 5.95C11.7833 6.30439 11.496 6.59167 11.1417 6.59167L0.641665 6.59167C0.287284 6.59167 -3.68276e-07 6.30439 -3.37294e-07 5.95C-3.06313e-07 5.59562 0.287284 5.30834 0.641665 5.30834L11.1417 5.30834C11.496 5.30834 11.7833 5.59562 11.7833 5.95Z" fill="${color}"/>
9
+ </g>
10
+ </svg>`;
11
+ }
12
+ const ICON_CONTENT_GETTERS = {
13
+ add: getAddIconContent
14
+ };
15
+ function isIconSupported(name) {
16
+ return Object.prototype.hasOwnProperty.call(ICON_CONTENT_GETTERS, name);
17
+ }
18
+ function Icon({ name, className, style }) {
19
+ const theme = useTheme();
20
+ const getContent = ICON_CONTENT_GETTERS[name];
21
+ if (!getContent) return null;
22
+ const color = 'dark' === theme ? '#777777' : '#999999';
23
+ return <svg className={clsx('doubao-icon', className)} style={style} content={getContent(color)}/>;
24
+ }
25
+ export { Icon, isIconSupported };
@@ -0,0 +1,7 @@
1
+ .doubao-icon {
2
+ flex-shrink: 0;
3
+ width: 14px;
4
+ height: 14px;
5
+ display: block;
6
+ }
7
+
@@ -0,0 +1,11 @@
1
+ import type { CSSProperties } from '@lynx-js/types';
2
+ export type IconName = 'add';
3
+ export interface IconProps {
4
+ /** 图标名称。 */
5
+ name: IconName;
6
+ /** 组件类名。 */
7
+ className?: string;
8
+ /** 组件样式。 */
9
+ style?: CSSProperties;
10
+ }
11
+ //# sourceMappingURL=types.d.ts.map
File without changes
@@ -1,3 +1,5 @@
1
+ export { Icon, type IconName, type IconProps } from './icon/index.js';
2
+ export { OptionGroup, type OptionGroupItem, type OptionGroupLargeItem, type OptionGroupMediumItem, type OptionGroupProps, type OptionGroupSize, type OptionGroupText, type OptionGroupValue } from './option-group/index.js';
1
3
  export { Price, type PricePrefixTone, type PriceProps, type PriceTone, type PriceValue } from './price/index.js';
2
4
  export { Tag, type TagProps, type TagTone, type TagValue } from './tag/index.js';
3
5
  export { Title, type TitleMaxLines, type TitleProps, type TitleValue } from './title/index.js';
@@ -1,3 +1,5 @@
1
+ export { Icon } from "./icon/index.js";
2
+ export { OptionGroup } from "./option-group/index.js";
1
3
  export { Price } from "./price/index.js";
2
4
  export { Tag } from "./tag/index.js";
3
5
  export { Title } from "./title/index.js";
@@ -0,0 +1,5 @@
1
+ import type { OptionGroupProps } from './types.js';
2
+ import './styles.scss';
3
+ export type { OptionGroupItem, OptionGroupLargeItem, OptionGroupMediumItem, OptionGroupProps, OptionGroupSize, OptionGroupText, OptionGroupValue } from './types.js';
4
+ export declare function OptionGroup(props: OptionGroupProps): any;
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { useThemeClassName } from "@byted-doubao-apps/components";
2
+ import { clsx } from "clsx";
3
+ import { Icon, isIconSupported } from "../icon/index.js";
4
+ import "./styles.css";
5
+ const OPTION_GAP = 8;
6
+ const DEFAULT_COLUMNS = 3;
7
+ const COLUMN_ROUNDING_TOLERANCE = 0.5;
8
+ function resolveColumns(columns) {
9
+ return null != columns && Number.isFinite(columns) && columns >= 1 ? Math.floor(columns) : DEFAULT_COLUMNS;
10
+ }
11
+ function getLargeOptionStyle(columns) {
12
+ const reservedWidth = (columns - 1) * OPTION_GAP + COLUMN_ROUNDING_TOLERANCE;
13
+ const width = `calc((100% - ${reservedWidth}px) / ${columns})`;
14
+ return {
15
+ width,
16
+ flexBasis: width
17
+ };
18
+ }
19
+ function isSelected(props, value) {
20
+ if ('multiple' === props.selectionMode) return props.value?.includes(value) ?? false;
21
+ return props.value === value;
22
+ }
23
+ function handleOptionClick(props, item, event) {
24
+ if ('multiple' === props.selectionMode) {
25
+ const currentValue = props.value ?? [];
26
+ const selected = currentValue.includes(item.value);
27
+ const maxSelected = null != props.maxSelected && Number.isFinite(props.maxSelected) && props.maxSelected >= 1 ? Math.floor(props.maxSelected) : void 0;
28
+ if (selected) props.onChange?.(currentValue.filter((value)=>value !== item.value), item, event);
29
+ else if (null == maxSelected || currentValue.length < maxSelected) props.onChange?.([
30
+ ...currentValue,
31
+ item.value
32
+ ], item, event);
33
+ } else if (props.value !== item.value) props.onChange?.(item.value, item, event);
34
+ item.onClick?.(event);
35
+ }
36
+ function MediumOptionItemView({ item, selected, onClick }) {
37
+ const hasIcon = Boolean(item.icon) && isIconSupported(item.icon ?? '');
38
+ return <view className={clsx('doubao-option-group__item', 'doubao-option-group__item--size-md', {
39
+ 'doubao-option-group__item--selected': selected,
40
+ 'doubao-option-group__item--disabled': item.disabled,
41
+ 'doubao-option-group__item--with-icon': hasIcon
42
+ })} bindtap={item.disabled ? void 0 : onClick}>
43
+ {hasIcon && item.icon ? <Icon className="doubao-option-group__icon" name={item.icon}/> : null}
44
+ <view className="doubao-option-group__content">
45
+ <text className="doubao-option-group__title" text-maxline="1">
46
+ {item.title}
47
+ </text>
48
+ </view>
49
+ </view>;
50
+ }
51
+ function LargeOptionItemView({ item, selected, style, onClick }) {
52
+ return <view className={clsx('doubao-option-group__item', 'doubao-option-group__item--size-lg', {
53
+ 'doubao-option-group__item--selected': selected,
54
+ 'doubao-option-group__item--disabled': item.disabled
55
+ })} style={style} bindtap={item.disabled ? void 0 : onClick}>
56
+ <view className="doubao-option-group__content">
57
+ <text className="doubao-option-group__title" text-maxline="1">
58
+ {item.title}
59
+ </text>
60
+ {void 0 !== item.subtitle ? <text className="doubao-option-group__subtitle" text-maxline="1">
61
+ {item.subtitle}
62
+ </text> : null}
63
+ </view>
64
+ </view>;
65
+ }
66
+ function renderOptionViews(props) {
67
+ if ('lg' === props.size) {
68
+ const optionStyle = getLargeOptionStyle(resolveColumns(props.columns));
69
+ return props.items.map((item)=>{
70
+ const selected = isSelected(props, item.value);
71
+ const onClick = (event)=>handleOptionClick(props, item, event);
72
+ return <LargeOptionItemView key={item.value} item={item} selected={selected} style={optionStyle} onClick={onClick}/>;
73
+ });
74
+ }
75
+ return props.items.map((item)=>{
76
+ const selected = isSelected(props, item.value);
77
+ const onClick = (event)=>handleOptionClick(props, item, event);
78
+ return <MediumOptionItemView key={item.value} item={item} selected={selected} onClick={onClick}/>;
79
+ });
80
+ }
81
+ function OptionGroup(props) {
82
+ const { className, style } = props;
83
+ const size = props.size ?? 'md';
84
+ const themedClassName = useThemeClassName(clsx('doubao-option-group', `doubao-option-group--size-${size}`, className));
85
+ const optionViews = renderOptionViews(props);
86
+ return <view className={themedClassName} style={style}>
87
+ {optionViews}
88
+ </view>;
89
+ }
90
+ export { OptionGroup };
@@ -0,0 +1,98 @@
1
+ .doubao-option-group {
2
+ box-sizing: border-box;
3
+ flex-flow: wrap;
4
+ align-items: center;
5
+ gap: 8px;
6
+ width: 100%;
7
+ min-width: 0;
8
+ display: flex;
9
+ }
10
+
11
+ .doubao-option-group--size-lg {
12
+ align-items: flex-start;
13
+ }
14
+
15
+ .doubao-option-group__item {
16
+ box-sizing: border-box;
17
+ background-color: var(--bg-base-2-overlay);
18
+ border: .5px solid #0000;
19
+ border-radius: 8px;
20
+ justify-content: center;
21
+ align-items: center;
22
+ min-width: 0;
23
+ display: flex;
24
+ overflow: hidden;
25
+ }
26
+
27
+ .doubao-option-group__item--size-md {
28
+ width: max-content;
29
+ max-width: 100%;
30
+ height: 44px;
31
+ padding: 12px 16px;
32
+ }
33
+
34
+ .doubao-option-group__item--size-lg {
35
+ border-width: 1px;
36
+ flex-grow: 0;
37
+ flex-shrink: 0;
38
+ height: 62px;
39
+ padding: 12px 4px;
40
+ }
41
+
42
+ .doubao-option-group__item--with-icon {
43
+ gap: 4px;
44
+ padding-left: 12px;
45
+ }
46
+
47
+ .doubao-option-group__item--selected {
48
+ background-color: var(--bg-base-1);
49
+ border-color: var(--primary-50);
50
+ background-image: linear-gradient(#0066ff0f, #0066ff0f);
51
+ }
52
+
53
+ .doubao-option-group__item--disabled {
54
+ opacity: .3;
55
+ }
56
+
57
+ .doubao-option-group__icon {
58
+ width: 14px;
59
+ height: 14px;
60
+ }
61
+
62
+ .doubao-option-group__content {
63
+ flex-direction: column;
64
+ align-items: center;
65
+ gap: 2px;
66
+ min-width: 0;
67
+ max-width: 100%;
68
+ display: flex;
69
+ }
70
+
71
+ .doubao-option-group__title, .doubao-option-group__subtitle {
72
+ text-align: center;
73
+ text-overflow: ellipsis;
74
+ white-space: nowrap;
75
+ min-width: 0;
76
+ max-width: 100%;
77
+ font-family: PingFang SC, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, sans-serif;
78
+ font-size: 13px;
79
+ font-style: normal;
80
+ line-height: 18px;
81
+ display: block;
82
+ overflow: hidden;
83
+ }
84
+
85
+ .doubao-option-group__title {
86
+ color: var(--neutral-100);
87
+ font-weight: 500;
88
+ }
89
+
90
+ .doubao-option-group__subtitle {
91
+ color: var(--neutral-70);
92
+ font-weight: 400;
93
+ }
94
+
95
+ .doubao-option-group__item--size-lg .doubao-option-group__content, .doubao-option-group__item--size-lg .doubao-option-group__title, .doubao-option-group__item--size-lg .doubao-option-group__subtitle {
96
+ width: 100%;
97
+ }
98
+
@@ -0,0 +1,71 @@
1
+ import type { CSSProperties, TouchEvent } from '@lynx-js/types';
2
+ import type { IconName } from '../icon/index.js';
3
+ export type OptionGroupValue = string | number;
4
+ export type OptionGroupText = string | number;
5
+ export type OptionGroupSize = 'md' | 'lg';
6
+ interface OptionGroupItemCommon {
7
+ /** 选项唯一值。 */
8
+ value: OptionGroupValue;
9
+ /** 选项标题。 */
10
+ title: OptionGroupText;
11
+ /** 是否禁用。@defaultValue false */
12
+ disabled?: boolean;
13
+ /** 点击当前选项时触发。 */
14
+ onClick?: (event: TouchEvent) => void;
15
+ }
16
+ export interface OptionGroupMediumItem extends OptionGroupItemCommon {
17
+ /** 标题前图标名称,由 Icon Primitive 解析。 */
18
+ icon?: IconName;
19
+ subtitle?: never;
20
+ }
21
+ export interface OptionGroupLargeItem extends OptionGroupItemCommon {
22
+ /** 第二行辅助信息。 */
23
+ subtitle?: OptionGroupText;
24
+ icon?: never;
25
+ }
26
+ export type OptionGroupItem = OptionGroupMediumItem | OptionGroupLargeItem;
27
+ interface OptionGroupCommonProps {
28
+ /** 组件类名。 */
29
+ className?: string;
30
+ /** 组件样式。 */
31
+ style?: CSSProperties;
32
+ }
33
+ interface OptionGroupMediumLayoutProps extends OptionGroupCommonProps {
34
+ /** 选项尺寸。@defaultValue 'md' */
35
+ size?: 'md';
36
+ /** 中尺寸选项数据。 */
37
+ items: OptionGroupMediumItem[];
38
+ columns?: never;
39
+ }
40
+ interface OptionGroupLargeLayoutProps extends OptionGroupCommonProps {
41
+ /** 选项尺寸。 */
42
+ size: 'lg';
43
+ /** 大尺寸选项数据。 */
44
+ items: OptionGroupLargeItem[];
45
+ /** 每行展示栏数。@defaultValue 3 */
46
+ columns?: number;
47
+ }
48
+ interface OptionGroupSingleSelectionProps {
49
+ /** 选择模式。@defaultValue 'single' */
50
+ selectionMode?: 'single';
51
+ /** 当前选中的选项值。 */
52
+ value?: OptionGroupValue;
53
+ maxSelected?: never;
54
+ /** 选中项变化时触发。 */
55
+ onChange?: (value: OptionGroupValue, item: OptionGroupItem, event: TouchEvent) => void;
56
+ }
57
+ interface OptionGroupMultipleSelectionProps {
58
+ /** 多选模式。 */
59
+ selectionMode: 'multiple';
60
+ /** 当前选中的选项值。 */
61
+ value?: OptionGroupValue[];
62
+ /** 最大选择数量;双选时传 2。 */
63
+ maxSelected?: number;
64
+ /** 选中项变化时触发。 */
65
+ onChange?: (value: OptionGroupValue[], item: OptionGroupItem, event: TouchEvent) => void;
66
+ }
67
+ type OptionGroupLayoutProps = OptionGroupMediumLayoutProps | OptionGroupLargeLayoutProps;
68
+ type OptionGroupSelectionProps = OptionGroupSingleSelectionProps | OptionGroupMultipleSelectionProps;
69
+ export type OptionGroupProps = OptionGroupLayoutProps & OptionGroupSelectionProps;
70
+ export {};
71
+ //# sourceMappingURL=types.d.ts.map
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doubao-dev/template",
3
- "version": "0.0.42",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {