@doubao-dev/template 0.0.42 → 0.1.1
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 +112 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/map-card/index.d.ts +37 -0
- package/dist/map-card/index.js +43 -0
- package/dist/map-card/styles.css +110 -0
- package/dist/primitives/icon/index.d.ts +6 -0
- package/dist/primitives/icon/index.js +25 -0
- package/dist/primitives/icon/styles.css +7 -0
- package/dist/primitives/icon/types.d.ts +11 -0
- package/dist/primitives/icon/types.js +0 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +2 -0
- package/dist/primitives/option-group/index.d.ts +5 -0
- package/dist/primitives/option-group/index.js +90 -0
- package/dist/primitives/option-group/styles.css +98 -0
- package/dist/primitives/option-group/types.d.ts +71 -0
- package/dist/primitives/option-group/types.js +0 -0
- package/package.json +1 -1
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` 是业务展示组件,不包含卡片外壳。
|
|
@@ -119,9 +182,51 @@ import { Price } from '@doubao-dev/template/primitives';
|
|
|
119
182
|
- `loading` 展示最大宽度为 60% 的扫光占位。
|
|
120
183
|
- `className` 和 `style` 可用于样式定制。
|
|
121
184
|
|
|
185
|
+
## MapCard
|
|
186
|
+
|
|
187
|
+
地图卡模板,由固定高度地图、可选信息区和操作区组成,适合路线预估、叫车状态、位置详情等场景。
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { MapCard } from '@doubao-dev/template';
|
|
191
|
+
|
|
192
|
+
<MapCard
|
|
193
|
+
mapProps={{
|
|
194
|
+
center: { latitude: 39.9087, longitude: 116.3975 },
|
|
195
|
+
scale: 14,
|
|
196
|
+
polyline: [
|
|
197
|
+
{
|
|
198
|
+
id: 'route',
|
|
199
|
+
points: [
|
|
200
|
+
{ latitude: 39.9151, longitude: 116.4039 },
|
|
201
|
+
{ latitude: 39.9042, longitude: 116.3925 }
|
|
202
|
+
],
|
|
203
|
+
width: 5
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
}}
|
|
207
|
+
title="预估 29 元"
|
|
208
|
+
metaItems={[
|
|
209
|
+
{ text: '最高优惠 6 元', tone: 'highlight' },
|
|
210
|
+
{ text: '已选 4 种车型' }
|
|
211
|
+
]}
|
|
212
|
+
inlineActions={[{ text: '确认叫车', onClick: () => console.log('confirm') }]}
|
|
213
|
+
/>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
说明:
|
|
217
|
+
|
|
218
|
+
- `mapProps` 透传地图能力,但卡片统一管理地图的 `className`、`style` 和 168px 高度;可通过 `mapProps.children` 传入 `MapMarker` 等地图覆盖物。
|
|
219
|
+
- `ref` 会直接绑定到内部 Map,可通过 `MapRef` 调用 `getCenter`、`setCenter` 等实例方法。
|
|
220
|
+
- MapCard 不渲染地图左上角来源标题,小程序标题由客户端的 `titleType` 控制。
|
|
221
|
+
- `title` 使用单行 `Title` primitive,`showArrow` 默认是 `true`。
|
|
222
|
+
- `metaItems` 最多展示前 2 项,`tone="highlight"` 使用强调色;多项之间由模板补充分隔点。
|
|
223
|
+
- `inlineActions` 复用 `TemplateCardFooterActionButton`;单按钮按主按钮渲染,双按钮依次按次按钮、主按钮渲染。
|
|
224
|
+
- `footer` 使用通用底部操作区。信息区和 footer 可单独使用,也可组合使用。
|
|
225
|
+
- MapCard 不支持 `children`,地图配置、信息和操作均使用对应的结构化字段。
|
|
226
|
+
|
|
122
227
|
## ContentCard
|
|
123
228
|
|
|
124
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
229
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/content-card.png" alt="ContentCard 示例" width="400" />
|
|
125
230
|
|
|
126
231
|
通用列表卡,由标题栏、列表内容和底部操作区组成,适合商品、服务、内容入口等可重复条目。
|
|
127
232
|
|
|
@@ -163,7 +268,7 @@ import { ContentCard } from '@doubao-dev/template';
|
|
|
163
268
|
|
|
164
269
|
## PriceActionCard
|
|
165
270
|
|
|
166
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
271
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/price-action-card.png" alt="PriceActionCard 示例" width="400" />
|
|
167
272
|
|
|
168
273
|
价格操作卡,由多条"价格 + 信息 + 右侧按钮"组成,适合报价、权益、服务方案等结果列表。
|
|
169
274
|
|
|
@@ -196,7 +301,7 @@ import { PriceActionCard } from '@doubao-dev/template';
|
|
|
196
301
|
|
|
197
302
|
## CheckoutCard
|
|
198
303
|
|
|
199
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
304
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/checkout-card.png" alt="CheckoutCard 示例" width="400" />
|
|
200
305
|
|
|
201
306
|
通用提单卡,由商品摘要、提单信息、费用汇总和底部操作区组成,适合下单确认、支付确认等场景。
|
|
202
307
|
|
|
@@ -247,7 +352,7 @@ import { CheckoutCard } from '@doubao-dev/template';
|
|
|
247
352
|
|
|
248
353
|
## TicketOrderCard
|
|
249
354
|
|
|
250
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
355
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/ticket-checkout-card.png" alt="TicketOrderCard 示例" width="400" />
|
|
251
356
|
|
|
252
357
|
> **已废弃:** 请使用 `CheckoutCard`。`TicketOrderCard` 仅为兼容现有代码保留。
|
|
253
358
|
|
|
@@ -294,7 +399,7 @@ import { TicketOrderCard } from '@doubao-dev/template';
|
|
|
294
399
|
|
|
295
400
|
## TransitCard
|
|
296
401
|
|
|
297
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
402
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/transit-card.png" alt="TransitCard 示例" width="400" />
|
|
298
403
|
|
|
299
404
|
交通票务列表卡,由多条出发、中转、到达和价格信息组成,适合展示机票、火车票、大巴等推荐列表。
|
|
300
405
|
|
|
@@ -342,7 +447,7 @@ import { TransitCard } from '@doubao-dev/template';
|
|
|
342
447
|
|
|
343
448
|
## AskHumanCard
|
|
344
449
|
|
|
345
|
-
<img src="https://unpkg.com/@doubao-dev/template@0.
|
|
450
|
+
<img src="https://unpkg.com/@doubao-dev/template@0.1.1/assets/ask-human-card.png" alt="AskHumanCard 示例" width="400" />
|
|
346
451
|
|
|
347
452
|
人工确认选项卡,由一组选项组成,适合让用户在多个候选项中确认选择。
|
|
348
453
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type { TemplateExtraAction, TemplateExtraActionButton, TemplateExtraActio
|
|
|
5
5
|
export { AskHumanCard, type AskHumanCardArrowPosition, type AskHumanCardItem, type AskHumanCardProps } from './ask-human-card/index.js';
|
|
6
6
|
export { CheckoutCard, type CheckoutCardFeeItem, type CheckoutCardInfoItem, type CheckoutCardProductItem, type CheckoutCardProps } from './checkout-card/index.js';
|
|
7
7
|
export { PriceActionCard, type PriceActionCardInfoRow, type PriceActionCardItem, type PriceActionCardProps } from './price-action-card/index.js';
|
|
8
|
+
export { MapCard, type MapCardMapProps, type MapCardMetaItem, type MapCardMetaTone, type MapCardProps } from './map-card/index.js';
|
|
8
9
|
export { TransitCard, type TransitCardItem, type TransitCardPrice, type TransitCardProps, type TransitCardTag } from './transit-card/index.js';
|
|
9
10
|
export type { TemplateKey, TemplateTagTone, TemplateText } from './types.js';
|
|
10
11
|
export { TicketOrderCard, type TicketOrderCardFeeItem, type TicketOrderCardInfoItem, type TicketOrderCardProps, type TicketOrderCardRouteItem } from './ticket-order-card/index.js';
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { ContentCard } from "./content-card/index.js";
|
|
|
2
2
|
export { AskHumanCard } from "./ask-human-card/index.js";
|
|
3
3
|
export { CheckoutCard } from "./checkout-card/index.js";
|
|
4
4
|
export { PriceActionCard } from "./price-action-card/index.js";
|
|
5
|
+
export { MapCard } from "./map-card/index.js";
|
|
5
6
|
export { TransitCard } from "./transit-card/index.js";
|
|
6
7
|
export { TicketOrderCard } from "./ticket-order-card/index.js";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type MapProps, type MapRef } from '@byted-doubao-apps/components';
|
|
2
|
+
import type { CSSProperties, TouchEvent } from '@lynx-js/types';
|
|
3
|
+
import { type TemplateCardFooterActionButton, type TemplateCardFooterProps } from '../components/footer/index.js';
|
|
4
|
+
import type { TemplateKey, TemplateText } from '../types.js';
|
|
5
|
+
import './styles.scss';
|
|
6
|
+
export type MapCardMapProps = Omit<MapProps, 'className' | 'style'>;
|
|
7
|
+
export type MapCardMetaTone = 'default' | 'highlight';
|
|
8
|
+
export interface MapCardMetaItem {
|
|
9
|
+
/** 唯一标识;不传时使用数组下标。 */
|
|
10
|
+
key?: TemplateKey;
|
|
11
|
+
/** 辅助信息文案。 */
|
|
12
|
+
text: TemplateText;
|
|
13
|
+
/** 文案色调。@defaultValue default */
|
|
14
|
+
tone?: MapCardMetaTone;
|
|
15
|
+
}
|
|
16
|
+
export interface MapCardProps {
|
|
17
|
+
/** 地图配置。卡片统一管理地图的宽高和类名。 */
|
|
18
|
+
mapProps: MapCardMapProps;
|
|
19
|
+
/** 信息区标题。 */
|
|
20
|
+
title?: TemplateText;
|
|
21
|
+
/** 是否展示标题右侧箭头。@defaultValue true */
|
|
22
|
+
showArrow?: boolean;
|
|
23
|
+
/** 标题下方的辅助信息,最多展示前两项。 */
|
|
24
|
+
metaItems?: MapCardMetaItem[];
|
|
25
|
+
/** 信息区右侧操作,最多展示前两个;双按钮时依次按次按钮、主按钮渲染。 */
|
|
26
|
+
inlineActions?: TemplateCardFooterActionButton[];
|
|
27
|
+
/** 底部操作区配置。 */
|
|
28
|
+
footer?: TemplateCardFooterProps;
|
|
29
|
+
/** 卡片根节点自定义类名。 */
|
|
30
|
+
className?: string;
|
|
31
|
+
/** 卡片根节点自定义样式。 */
|
|
32
|
+
style?: CSSProperties;
|
|
33
|
+
/** 点击卡片根节点时触发。 */
|
|
34
|
+
onClick?: (event: TouchEvent) => void;
|
|
35
|
+
}
|
|
36
|
+
export declare const MapCard: (props: MapCardProps & import("react").RefAttributes<MapRef>) => React.ReactElement | null;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Button, Map, useThemeClassName } from "@byted-doubao-apps/components";
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { TemplateCardFooter } from "../components/footer/index.js";
|
|
5
|
+
import { Title } from "../primitives/title/index.js";
|
|
6
|
+
import "./styles.css";
|
|
7
|
+
function hasTemplateText(value) {
|
|
8
|
+
return void 0 !== value && '' !== value;
|
|
9
|
+
}
|
|
10
|
+
function MapCardImpl(props, ref) {
|
|
11
|
+
const { mapProps, title, showArrow = true, metaItems = [], inlineActions = [], footer, className, style, onClick } = props;
|
|
12
|
+
const visibleMetaItems = metaItems.filter((item)=>hasTemplateText(item.text)).slice(0, 2);
|
|
13
|
+
const visibleInlineActions = inlineActions.filter((action)=>null != action.text && action.text.length > 0).slice(0, 2);
|
|
14
|
+
const hasTitle = hasTemplateText(title);
|
|
15
|
+
const hasContent = hasTitle || visibleMetaItems.length > 0 || visibleInlineActions.length > 0;
|
|
16
|
+
return <view className={useThemeClassName(clsx('doubao-map-card', className))} style={style} bindtap={onClick} clip-radius="true">
|
|
17
|
+
<view className="doubao-map-card__viewport">
|
|
18
|
+
<Map {...mapProps} ref={ref} className="doubao-map-card__map"/>
|
|
19
|
+
</view>
|
|
20
|
+
|
|
21
|
+
{hasContent ? <view className="doubao-map-card__content">
|
|
22
|
+
<view className="doubao-map-card__text-content">
|
|
23
|
+
{hasTitle ? <Title text={title} showArrow={showArrow}/> : null}
|
|
24
|
+
{visibleMetaItems.length > 0 ? <view className="doubao-map-card__meta">
|
|
25
|
+
{visibleMetaItems.map((item, index)=><view className="doubao-map-card__meta-entry" key={item.key ?? index}>
|
|
26
|
+
{index > 0 ? <text className="doubao-map-card__meta-separator">·</text> : null}
|
|
27
|
+
<text className={clsx('doubao-map-card__meta-text', 'highlight' === item.tone && 'doubao-map-card__meta-text--highlight')} text-maxline="1">
|
|
28
|
+
{item.text}
|
|
29
|
+
</text>
|
|
30
|
+
</view>)}
|
|
31
|
+
</view> : null}
|
|
32
|
+
</view>
|
|
33
|
+
|
|
34
|
+
{visibleInlineActions.length > 0 ? <view className="doubao-map-card__inline-actions">
|
|
35
|
+
{visibleInlineActions.map((action, index)=><Button className="doubao-map-card__inline-action" key={index} size="mini" type={visibleInlineActions.length > 1 && 0 === index ? 'default' : 'primary'} text={action.text ?? ''} disabled={action.disabled} loading={action.loading} throttle={action.throttle} onClick={action.onClick}/>)}
|
|
36
|
+
</view> : null}
|
|
37
|
+
</view> : null}
|
|
38
|
+
|
|
39
|
+
<TemplateCardFooter primaryActionButton={footer?.primaryActionButton} secondaryActionButton={footer?.secondaryActionButton}/>
|
|
40
|
+
</view>;
|
|
41
|
+
}
|
|
42
|
+
const MapCard = forwardRef(MapCardImpl);
|
|
43
|
+
export { MapCard };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.doubao-map-card {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
background-color: var(--bg-base-1);
|
|
4
|
+
border: 1px solid var(--bg-base-2-overlay);
|
|
5
|
+
border-radius: 8px;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
align-items: stretch;
|
|
8
|
+
width: 100%;
|
|
9
|
+
display: flex;
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.doubao-map-card__viewport {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
border: 1px solid var(--bg-base-2-overlay);
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 168px;
|
|
19
|
+
position: relative;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.doubao-map-card__map {
|
|
24
|
+
width: 100%;
|
|
25
|
+
height: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.doubao-map-card__content {
|
|
29
|
+
box-sizing: border-box;
|
|
30
|
+
background-color: var(--bg-base-1);
|
|
31
|
+
border-radius: 8px;
|
|
32
|
+
flex-direction: row;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: 12px;
|
|
35
|
+
width: 100%;
|
|
36
|
+
padding: 12px;
|
|
37
|
+
display: flex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.doubao-map-card__text-content {
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
flex: 1 1 0;
|
|
43
|
+
align-items: flex-start;
|
|
44
|
+
gap: 2px;
|
|
45
|
+
min-width: 0;
|
|
46
|
+
display: flex;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.doubao-map-card__meta {
|
|
51
|
+
flex-direction: row;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
align-items: center;
|
|
54
|
+
width: 100%;
|
|
55
|
+
min-width: 0;
|
|
56
|
+
height: 22px;
|
|
57
|
+
display: flex;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.doubao-map-card__meta-entry {
|
|
62
|
+
flex-direction: row;
|
|
63
|
+
flex: 0 auto;
|
|
64
|
+
align-items: center;
|
|
65
|
+
min-width: 0;
|
|
66
|
+
display: flex;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.doubao-map-card__meta-separator {
|
|
71
|
+
color: var(--neutral-70);
|
|
72
|
+
flex-shrink: 0;
|
|
73
|
+
margin: 0 4px;
|
|
74
|
+
font-family: PingFang SC, sans-serif;
|
|
75
|
+
font-size: 13px;
|
|
76
|
+
font-weight: 400;
|
|
77
|
+
line-height: 1.5;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.doubao-map-card__meta-text {
|
|
81
|
+
min-width: 0;
|
|
82
|
+
color: var(--neutral-70);
|
|
83
|
+
text-overflow: ellipsis;
|
|
84
|
+
white-space: nowrap;
|
|
85
|
+
font-family: PingFang SC, sans-serif;
|
|
86
|
+
font-size: 13px;
|
|
87
|
+
font-style: normal;
|
|
88
|
+
font-weight: 400;
|
|
89
|
+
line-height: 1.5;
|
|
90
|
+
overflow: hidden;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.doubao-map-card__meta-text--highlight {
|
|
94
|
+
color: #ff375f;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.doubao-map-card__inline-actions {
|
|
98
|
+
flex-direction: row;
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
align-items: center;
|
|
101
|
+
gap: 8px;
|
|
102
|
+
display: flex;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.doubao-map-card__inline-action {
|
|
106
|
+
width: auto;
|
|
107
|
+
min-width: 60px;
|
|
108
|
+
max-width: 100px;
|
|
109
|
+
}
|
|
110
|
+
|
|
@@ -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,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';
|
package/dist/primitives/index.js
CHANGED
|
@@ -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
|