@shipload/item-renderer 1.0.0-next.3 → 1.0.0-next.30
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/package.json +2 -2
- package/src/index.ts +22 -8
- package/src/links.ts +11 -8
- package/src/meta.ts +1 -1
- package/src/payload/codec.ts +24 -2
- package/src/primitives/module-slot.ts +4 -4
- package/src/primitives/quantity-badge.ts +15 -5
- package/src/primitives/resource-icon.ts +242 -0
- package/src/primitives/span-paragraph.ts +1 -1
- package/src/primitives/stat-bar.ts +17 -1
- package/src/primitives/stat-stars.ts +36 -0
- package/src/primitives/text.ts +27 -1
- package/src/render.ts +12 -7
- package/src/templates/_shared.ts +113 -2
- package/src/templates/component.ts +41 -61
- package/src/templates/index.ts +2 -1
- package/src/templates/item-cell.ts +86 -38
- package/src/templates/module.ts +59 -145
- package/src/templates/packed-entity.ts +25 -5
- package/src/templates/resource.ts +40 -68
- package/src/templates/ship-panel.ts +80 -85
- package/src/tokens/colors.ts +4 -10
- package/test/fixtures/cargo-items.ts +3 -3
- package/src/primitives/category-icon.ts +0 -110
- package/src/primitives/compact-row.ts +0 -38
package/src/templates/_shared.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import type {ResolvedItem} from '@shipload/sdk'
|
|
2
|
+
import {baseName, formatMassScaled} from '@shipload/sdk'
|
|
3
|
+
import {text} from '../primitives/text.ts'
|
|
4
|
+
import {divider} from '../primitives/divider.ts'
|
|
1
5
|
import {tokens} from '../tokens/index.ts'
|
|
2
6
|
|
|
3
|
-
export function formatMass(
|
|
4
|
-
return
|
|
7
|
+
export function formatMass(kg: number): string {
|
|
8
|
+
return formatMassScaled(kg)
|
|
5
9
|
}
|
|
6
10
|
|
|
7
11
|
export function tierBorder(tier: number): string {
|
|
@@ -12,3 +16,110 @@ export function shortCode(itemId: number): string {
|
|
|
12
16
|
const str = itemId.toString(10)
|
|
13
17
|
return str.slice(-2).padStart(2, '0')
|
|
14
18
|
}
|
|
19
|
+
|
|
20
|
+
export function capabilityColor(name: string): string {
|
|
21
|
+
const key = name.toLowerCase().replace(/\s+/g, '') as keyof typeof tokens.colors.capability
|
|
22
|
+
return tokens.colors.capability[key] ?? tokens.colors.accent.component
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const META_ROW_H = 22
|
|
26
|
+
export const HEADER_H = 48
|
|
27
|
+
export const ICON_Y = 4
|
|
28
|
+
export const BADGE_Y = 6
|
|
29
|
+
export const META_BLOCK_GAP = 16
|
|
30
|
+
|
|
31
|
+
export const STAT_ROW_H = 26
|
|
32
|
+
export const CAP_HEADER_H = 22
|
|
33
|
+
export const CAP_ROW_H = 18
|
|
34
|
+
export const BODY_TAIL = 8
|
|
35
|
+
|
|
36
|
+
// Gap from the meta block to the first stat row. Resources/components have no
|
|
37
|
+
// body sub-header, and statBar draws its label 6px above its y, so this is
|
|
38
|
+
// META_BLOCK_GAP plus the offset that puts the first stat label level with
|
|
39
|
+
// where module/entity body sections begin — keeping the meta→body gap uniform.
|
|
40
|
+
export const STAT_BLOCK_GAP = META_BLOCK_GAP + 22
|
|
41
|
+
|
|
42
|
+
// Uniform gap between a card's last body element and the bottom frame edge.
|
|
43
|
+
// Cards size their height to (last element bottom) + BOTTOM_PAD so trailing
|
|
44
|
+
// space is consistent across resource / component / module / entity types.
|
|
45
|
+
export const BOTTOM_PAD = 22
|
|
46
|
+
|
|
47
|
+
export function titleParts(x: number, y: number, name: string, tier: number): string {
|
|
48
|
+
return text({
|
|
49
|
+
x,
|
|
50
|
+
y,
|
|
51
|
+
value: name,
|
|
52
|
+
size: tokens.typography.sizes.title,
|
|
53
|
+
weight: 700,
|
|
54
|
+
family: tokens.typography.display,
|
|
55
|
+
spans: [
|
|
56
|
+
{
|
|
57
|
+
value: `T${tier}`,
|
|
58
|
+
dx: 6,
|
|
59
|
+
size: tokens.typography.sizes.subtitle,
|
|
60
|
+
weight: 700,
|
|
61
|
+
color: tokens.colors.text.secondary,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function titleText(x: number, y: number, resolved: ResolvedItem): string {
|
|
68
|
+
// Prominent base name; tier rendered as a smaller, muted inline suffix.
|
|
69
|
+
return titleParts(x, y, baseName(resolved), resolved.tier)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface MetaRowProps {
|
|
73
|
+
x: number
|
|
74
|
+
y: number
|
|
75
|
+
width: number
|
|
76
|
+
label: string
|
|
77
|
+
value: string
|
|
78
|
+
showDivider?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function metaRow({x, y, width, label, value, showDivider = true}: MetaRowProps): string {
|
|
82
|
+
const labelText = text({
|
|
83
|
+
x,
|
|
84
|
+
y: y + 12,
|
|
85
|
+
value: label,
|
|
86
|
+
size: tokens.typography.sizes.body,
|
|
87
|
+
color: tokens.colors.text.secondary,
|
|
88
|
+
})
|
|
89
|
+
const valueText = text({
|
|
90
|
+
x: x + width,
|
|
91
|
+
y: y + 12,
|
|
92
|
+
value,
|
|
93
|
+
size: tokens.typography.sizes.body,
|
|
94
|
+
weight: 700,
|
|
95
|
+
anchor: 'end',
|
|
96
|
+
})
|
|
97
|
+
const sep = showDivider
|
|
98
|
+
? divider({
|
|
99
|
+
x,
|
|
100
|
+
y: y + META_ROW_H - 4,
|
|
101
|
+
width,
|
|
102
|
+
color: tokens.colors.surface.panelBorderBright,
|
|
103
|
+
})
|
|
104
|
+
: ''
|
|
105
|
+
return labelText + valueText + sep
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function metaRowBlock(
|
|
109
|
+
x: number,
|
|
110
|
+
yStart: number,
|
|
111
|
+
width: number,
|
|
112
|
+
rows: {label: string; value: string}[]
|
|
113
|
+
): {svg: string; height: number} {
|
|
114
|
+
let svg = ''
|
|
115
|
+
rows.forEach((row, i) => {
|
|
116
|
+
svg += metaRow({
|
|
117
|
+
x,
|
|
118
|
+
y: yStart + i * META_ROW_H,
|
|
119
|
+
width,
|
|
120
|
+
...row,
|
|
121
|
+
showDivider: i < rows.length - 1,
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
return {svg, height: rows.length * META_ROW_H}
|
|
125
|
+
}
|
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
import type {ResolvedItem
|
|
2
|
-
import {
|
|
1
|
+
import type {ResolvedItem} from '@shipload/sdk'
|
|
2
|
+
import {getRecipe, getStatDefinitions, resolveItemCategory, formatLocation} from '@shipload/sdk'
|
|
3
3
|
import type {CargoItem} from '../payload/codec.ts'
|
|
4
4
|
import {panel} from '../primitives/panel.ts'
|
|
5
5
|
import {iconHex} from '../primitives/icon-hex.ts'
|
|
6
|
-
import {text} from '../primitives/text.ts'
|
|
7
|
-
import {divider} from '../primitives/divider.ts'
|
|
8
6
|
import {statBar} from '../primitives/stat-bar.ts'
|
|
9
7
|
import {quantityBadge} from '../primitives/quantity-badge.ts'
|
|
10
8
|
import {tokens} from '../tokens/index.ts'
|
|
11
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
shortCode,
|
|
11
|
+
formatMass,
|
|
12
|
+
tierBorder,
|
|
13
|
+
metaRowBlock,
|
|
14
|
+
titleText,
|
|
15
|
+
BADGE_Y,
|
|
16
|
+
HEADER_H,
|
|
17
|
+
ICON_Y,
|
|
18
|
+
STAT_BLOCK_GAP,
|
|
19
|
+
STAT_ROW_H,
|
|
20
|
+
BOTTOM_PAD,
|
|
21
|
+
} from './_shared.ts'
|
|
12
22
|
|
|
13
23
|
export interface RenderComponentOpts {
|
|
14
24
|
mode?: 'values' | 'ranges'
|
|
25
|
+
location?: {x: number; y: number}
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
type StatRow = {
|
|
@@ -32,13 +43,15 @@ export function renderComponent(
|
|
|
32
43
|
const pad = tokens.spacing.panelPadding
|
|
33
44
|
const innerW = w - pad * 2
|
|
34
45
|
|
|
46
|
+
const identity = tokens.colors.accent.component
|
|
47
|
+
|
|
35
48
|
let rows: StatRow[]
|
|
36
49
|
if (mode === 'values') {
|
|
37
50
|
rows = (resolved.stats ?? []).map((s) => ({
|
|
38
51
|
label: s.label,
|
|
39
52
|
abbreviation: s.abbreviation,
|
|
40
53
|
value: s.value,
|
|
41
|
-
color:
|
|
54
|
+
color: identity,
|
|
42
55
|
inverted: s.inverted,
|
|
43
56
|
}))
|
|
44
57
|
} else {
|
|
@@ -47,8 +60,9 @@ export function renderComponent(
|
|
|
47
60
|
const src = slot.sources[0]
|
|
48
61
|
if (!src) return []
|
|
49
62
|
const input = recipe!.inputs[src.inputIndex]
|
|
50
|
-
if (!input
|
|
51
|
-
const category = input.
|
|
63
|
+
if (!input) return []
|
|
64
|
+
const category = resolveItemCategory(input.itemId)
|
|
65
|
+
if (!category) return []
|
|
52
66
|
const def = getStatDefinitions(category)[src.statIndex]
|
|
53
67
|
if (!def) return []
|
|
54
68
|
return [
|
|
@@ -56,78 +70,44 @@ export function renderComponent(
|
|
|
56
70
|
label: def.label,
|
|
57
71
|
abbreviation: def.abbreviation,
|
|
58
72
|
value: null,
|
|
59
|
-
color:
|
|
73
|
+
color: identity,
|
|
60
74
|
inverted: def.inverted,
|
|
61
75
|
},
|
|
62
76
|
]
|
|
63
77
|
})
|
|
64
78
|
}
|
|
65
79
|
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
const quantity = Number(BigInt(item.quantity.toString()))
|
|
81
|
+
const metaRows = [
|
|
82
|
+
{label: 'Mass', value: formatMass(resolved.mass * Math.max(quantity, 1))},
|
|
83
|
+
...(opts?.location ? [{label: 'Location', value: formatLocation(opts.location)}] : []),
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
const metaYStart = pad + HEADER_H
|
|
87
|
+
const {svg: metaSvg, height: metaH} = metaRowBlock(pad, metaYStart, innerW, metaRows)
|
|
88
|
+
const statsYStart = metaYStart + metaH + STAT_BLOCK_GAP
|
|
89
|
+
const statsBottom =
|
|
90
|
+
statsYStart + Math.max(0, rows.length - 1) * STAT_ROW_H + tokens.spacing.statBarHeight
|
|
91
|
+
const height = statsBottom + BOTTOM_PAD
|
|
70
92
|
|
|
71
93
|
const chrome = panel({width: w, height, borderColor: tierBorder(resolved.tier)})
|
|
72
94
|
|
|
73
|
-
const
|
|
74
|
-
const badge = quantityBadge({x: w - pad, y: pad, quantity})
|
|
95
|
+
const badge = quantityBadge({x: w - pad, y: pad + BADGE_Y, quantity, tone: identity})
|
|
75
96
|
|
|
76
97
|
const icon = iconHex({
|
|
77
98
|
x: pad,
|
|
78
|
-
y: pad +
|
|
79
|
-
color:
|
|
99
|
+
y: pad + ICON_Y,
|
|
100
|
+
color: identity,
|
|
80
101
|
code: shortCode(resolved.itemId),
|
|
81
102
|
})
|
|
82
103
|
|
|
83
|
-
const name =
|
|
84
|
-
x: pad + 34,
|
|
85
|
-
y: pad + 22,
|
|
86
|
-
value: resolved.name,
|
|
87
|
-
size: tokens.typography.sizes.title,
|
|
88
|
-
weight: 700,
|
|
89
|
-
family: tokens.typography.display,
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
const subtitleText = text({
|
|
93
|
-
x: pad,
|
|
94
|
-
y: pad + headerH + 4,
|
|
95
|
-
value: 'Type',
|
|
96
|
-
size: tokens.typography.sizes.body,
|
|
97
|
-
color: tokens.colors.text.secondary,
|
|
98
|
-
})
|
|
99
|
-
const subtitleValue = text({
|
|
100
|
-
x: w - pad,
|
|
101
|
-
y: pad + headerH + 4,
|
|
102
|
-
value: `COMPONENT · ${formatTier(resolved.tier)}`,
|
|
103
|
-
size: tokens.typography.sizes.body,
|
|
104
|
-
weight: 600,
|
|
105
|
-
anchor: 'end',
|
|
106
|
-
})
|
|
107
|
-
const massLabel = text({
|
|
108
|
-
x: pad,
|
|
109
|
-
y: pad + headerH + metaRowH - 8,
|
|
110
|
-
value: 'Mass',
|
|
111
|
-
size: tokens.typography.sizes.body,
|
|
112
|
-
color: tokens.colors.text.secondary,
|
|
113
|
-
})
|
|
114
|
-
const massValue = text({
|
|
115
|
-
x: w - pad,
|
|
116
|
-
y: pad + headerH + metaRowH - 8,
|
|
117
|
-
value: formatMass(resolved.mass),
|
|
118
|
-
size: tokens.typography.sizes.body,
|
|
119
|
-
weight: 600,
|
|
120
|
-
anchor: 'end',
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
const sepY = pad + headerH + metaRowH + 6
|
|
124
|
-
const sep = divider({x: pad, y: sepY, width: innerW})
|
|
104
|
+
const name = titleText(pad + 34, pad + 22, resolved)
|
|
125
105
|
|
|
126
106
|
const statsSvg = rows
|
|
127
107
|
.map((row, i) =>
|
|
128
108
|
statBar({
|
|
129
109
|
x: pad,
|
|
130
|
-
y:
|
|
110
|
+
y: statsYStart + i * STAT_ROW_H,
|
|
131
111
|
width: innerW,
|
|
132
112
|
label: row.label,
|
|
133
113
|
abbreviation: row.abbreviation,
|
|
@@ -138,7 +118,7 @@ export function renderComponent(
|
|
|
138
118
|
)
|
|
139
119
|
.join('')
|
|
140
120
|
|
|
141
|
-
const inner = `${chrome}${icon}${name}${badge}${
|
|
121
|
+
const inner = `${chrome}${icon}${name}${badge}${metaSvg}${statsSvg}`
|
|
142
122
|
|
|
143
123
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${height}" viewBox="0 0 ${w} ${height}">${inner}</svg>`
|
|
144
124
|
}
|
package/src/templates/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {renderModule} from './module.ts'
|
|
|
8
8
|
|
|
9
9
|
export interface RenderByTypeOpts {
|
|
10
10
|
mode?: 'values' | 'ranges'
|
|
11
|
+
location?: {x: number; y: number}
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function renderByType(
|
|
@@ -19,7 +20,7 @@ export function renderByType(
|
|
|
19
20
|
case 'resource':
|
|
20
21
|
return renderResource(item, resolved, opts)
|
|
21
22
|
case 'entity':
|
|
22
|
-
return renderPackedEntity(item, resolved)
|
|
23
|
+
return renderPackedEntity(item, resolved, opts)
|
|
23
24
|
case 'component':
|
|
24
25
|
return renderComponent(item, resolved, opts)
|
|
25
26
|
case 'module':
|
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import type {ResolvedItem} from '@shipload/sdk'
|
|
2
|
-
import {tierColors
|
|
2
|
+
import {tierColors} from '@shipload/sdk'
|
|
3
3
|
import {el} from '../primitives/svg.ts'
|
|
4
4
|
import {text} from '../primitives/text.ts'
|
|
5
|
-
import {
|
|
5
|
+
import {resourceIcon} from '../primitives/resource-icon.ts'
|
|
6
6
|
import {tokens} from '../tokens/index.ts'
|
|
7
7
|
|
|
8
8
|
export interface ItemCellProps {
|
|
9
9
|
resolved: ResolvedItem
|
|
10
10
|
quantity?: number
|
|
11
11
|
size?: number
|
|
12
|
+
quantityColor?: string
|
|
13
|
+
quantityPrefix?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function abbreviateQuantity(n: number): string {
|
|
17
|
+
const abs = Math.abs(n)
|
|
18
|
+
if (abs < 1000) return String(n)
|
|
19
|
+
const units = [
|
|
20
|
+
{v: 1e9, s: 'b'},
|
|
21
|
+
{v: 1e6, s: 'm'},
|
|
22
|
+
{v: 1e3, s: 'k'},
|
|
23
|
+
]
|
|
24
|
+
for (const u of units) {
|
|
25
|
+
if (abs >= u.v) {
|
|
26
|
+
return (n / u.v).toFixed(1).replace(/\.0$/, '') + u.s
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return String(n)
|
|
12
30
|
}
|
|
13
31
|
|
|
14
32
|
export interface ItemCellGroupProps extends ItemCellProps {
|
|
@@ -34,37 +52,47 @@ function cellInner(props: ItemCellProps): string {
|
|
|
34
52
|
'stroke-width': 1.5,
|
|
35
53
|
})
|
|
36
54
|
|
|
55
|
+
const qty = props.quantity ?? 0
|
|
56
|
+
const showQuantity = props.quantityPrefix ? qty >= 1 : qty > 1
|
|
57
|
+
|
|
37
58
|
let content = ''
|
|
38
59
|
if (props.resolved.abbreviation) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
content = showQuantity
|
|
61
|
+
? text({
|
|
62
|
+
x: cx,
|
|
63
|
+
y: size * 0.45,
|
|
64
|
+
value: props.resolved.abbreviation,
|
|
65
|
+
size: Math.round(size * 0.28),
|
|
66
|
+
weight: 700,
|
|
67
|
+
anchor: 'middle',
|
|
68
|
+
color: tokens.colors.text.primary,
|
|
69
|
+
family: tokens.typography.display,
|
|
70
|
+
})
|
|
71
|
+
: text({
|
|
72
|
+
x: cx,
|
|
73
|
+
y: height / 2,
|
|
74
|
+
value: props.resolved.abbreviation,
|
|
75
|
+
size: Math.round(size * 0.36),
|
|
76
|
+
weight: 700,
|
|
77
|
+
anchor: 'middle',
|
|
78
|
+
dominantBaseline: 'central',
|
|
79
|
+
color: tokens.colors.text.primary,
|
|
80
|
+
family: tokens.typography.display,
|
|
81
|
+
})
|
|
50
82
|
} else if (props.resolved.category) {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
cy: iconCy,
|
|
58
|
-
size: size * 0.32,
|
|
59
|
-
color,
|
|
60
|
-
strokeWidth: 1.5,
|
|
83
|
+
const iconSize = Math.round(size * (showQuantity ? 0.66 : 0.84))
|
|
84
|
+
const iconY = showQuantity ? Math.round(size * 0.12) : Math.round(height / 2 - iconSize / 2)
|
|
85
|
+
content = resourceIcon(props.resolved.category, {
|
|
86
|
+
x: (size - iconSize) / 2,
|
|
87
|
+
y: iconY,
|
|
88
|
+
size: iconSize,
|
|
61
89
|
})
|
|
62
90
|
} else if (props.resolved.icon) {
|
|
63
91
|
content = text({
|
|
64
92
|
x: cx,
|
|
65
|
-
y: size * 0.4,
|
|
93
|
+
y: showQuantity ? size * 0.4 : height / 2,
|
|
66
94
|
value: props.resolved.icon,
|
|
67
|
-
size: Math.round(size * 0.44),
|
|
95
|
+
size: Math.round(size * (showQuantity ? 0.44 : 0.56)),
|
|
68
96
|
weight: 400,
|
|
69
97
|
anchor: 'middle',
|
|
70
98
|
dominantBaseline: 'central',
|
|
@@ -72,21 +100,41 @@ function cellInner(props: ItemCellProps): string {
|
|
|
72
100
|
})
|
|
73
101
|
}
|
|
74
102
|
|
|
75
|
-
const qty = props.quantity ?? 0
|
|
76
103
|
let quantityText = ''
|
|
77
|
-
if (
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
104
|
+
if (showQuantity) {
|
|
105
|
+
const label = (props.quantityPrefix ?? '') + abbreviateQuantity(qty)
|
|
106
|
+
const fontSize = Math.max(8, Math.round(size * 0.2))
|
|
107
|
+
const charW = fontSize * 0.6
|
|
108
|
+
const padX = Math.max(2, Math.round(fontSize * 0.34))
|
|
109
|
+
const padY = Math.max(1, Math.round(fontSize * 0.18))
|
|
110
|
+
const margin = Math.max(2, Math.round(size * 0.06))
|
|
111
|
+
const plateW = Math.round(label.length * charW) + padX * 2
|
|
112
|
+
const plateH = fontSize + padY * 2
|
|
113
|
+
const plateRight = size - margin
|
|
114
|
+
const plateBottom = height - margin
|
|
115
|
+
const plate = el('rect', {
|
|
116
|
+
x: plateRight - plateW,
|
|
117
|
+
y: plateBottom - plateH,
|
|
118
|
+
width: plateW,
|
|
119
|
+
height: plateH,
|
|
120
|
+
rx: Math.round(plateH / 2),
|
|
121
|
+
ry: Math.round(plateH / 2),
|
|
122
|
+
fill: '#050c24',
|
|
123
|
+
'fill-opacity': 0.82,
|
|
89
124
|
})
|
|
125
|
+
quantityText =
|
|
126
|
+
plate +
|
|
127
|
+
text({
|
|
128
|
+
x: plateRight - padX,
|
|
129
|
+
y: plateBottom - plateH / 2,
|
|
130
|
+
value: label,
|
|
131
|
+
size: fontSize,
|
|
132
|
+
weight: 700,
|
|
133
|
+
anchor: 'end',
|
|
134
|
+
dominantBaseline: 'central',
|
|
135
|
+
color: props.quantityColor ?? tokens.colors.text.primary,
|
|
136
|
+
family: tokens.typography.mono,
|
|
137
|
+
})
|
|
90
138
|
}
|
|
91
139
|
|
|
92
140
|
return border + content + quantityText
|