@oxy.so/bloom 4.17.0 → 4.18.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.
Files changed (191) hide show
  1. package/docs/adoption-matrix.mdx +4 -4
  2. package/docs/breadcrumb.mdx +11 -0
  3. package/docs/cart-panel.mdx +4 -2
  4. package/docs/checkout-summary.mdx +4 -0
  5. package/docs/chip.mdx +14 -1
  6. package/docs/dialog.mdx +5 -4
  7. package/docs/loading.mdx +11 -0
  8. package/docs/price-breakdown.mdx +17 -0
  9. package/docs/rating.mdx +22 -0
  10. package/docs/stepper.mdx +25 -0
  11. package/lib/commonjs/breadcrumb/Breadcrumb.js +36 -6
  12. package/lib/commonjs/breadcrumb/Breadcrumb.js.map +1 -1
  13. package/lib/commonjs/cart-panel/CartLine.js +32 -10
  14. package/lib/commonjs/cart-panel/CartLine.js.map +1 -1
  15. package/lib/commonjs/cart-panel/CartPanel.js +3 -0
  16. package/lib/commonjs/cart-panel/CartPanel.js.map +1 -1
  17. package/lib/commonjs/cart-panel/shared.js +2 -0
  18. package/lib/commonjs/cart-panel/shared.js.map +1 -1
  19. package/lib/commonjs/checkout-summary/CheckoutConfirm.js +11 -1
  20. package/lib/commonjs/checkout-summary/CheckoutConfirm.js.map +1 -1
  21. package/lib/commonjs/chip/Chip.js +3 -1
  22. package/lib/commonjs/chip/Chip.js.map +1 -1
  23. package/lib/commonjs/dialog/Dialog.js +15 -6
  24. package/lib/commonjs/dialog/Dialog.js.map +1 -1
  25. package/lib/commonjs/dialog/Dialog.web.js +11 -5
  26. package/lib/commonjs/dialog/Dialog.web.js.map +1 -1
  27. package/lib/commonjs/dialog/placement.js +18 -1
  28. package/lib/commonjs/dialog/placement.js.map +1 -1
  29. package/lib/commonjs/loading/Loading.js +7 -3
  30. package/lib/commonjs/loading/Loading.js.map +1 -1
  31. package/lib/commonjs/loading/Loading.web.js +7 -3
  32. package/lib/commonjs/loading/Loading.web.js.map +1 -1
  33. package/lib/commonjs/loading/LoadingBase.js +49 -2
  34. package/lib/commonjs/loading/LoadingBase.js.map +1 -1
  35. package/lib/commonjs/price-breakdown/PriceSummary.js +11 -1
  36. package/lib/commonjs/price-breakdown/PriceSummary.js.map +1 -1
  37. package/lib/commonjs/price-breakdown/PriceSummaryLine.js +13 -1
  38. package/lib/commonjs/price-breakdown/PriceSummaryLine.js.map +1 -1
  39. package/lib/commonjs/rating/Rating.js +90 -4
  40. package/lib/commonjs/rating/Rating.js.map +1 -1
  41. package/lib/commonjs/stepper/Stepper.js +18 -5
  42. package/lib/commonjs/stepper/Stepper.js.map +1 -1
  43. package/lib/module/breadcrumb/Breadcrumb.js +36 -6
  44. package/lib/module/breadcrumb/Breadcrumb.js.map +1 -1
  45. package/lib/module/cart-panel/CartLine.js +32 -10
  46. package/lib/module/cart-panel/CartLine.js.map +1 -1
  47. package/lib/module/cart-panel/CartPanel.js +3 -0
  48. package/lib/module/cart-panel/CartPanel.js.map +1 -1
  49. package/lib/module/cart-panel/shared.js +2 -0
  50. package/lib/module/cart-panel/shared.js.map +1 -1
  51. package/lib/module/checkout-summary/CheckoutConfirm.js +11 -1
  52. package/lib/module/checkout-summary/CheckoutConfirm.js.map +1 -1
  53. package/lib/module/chip/Chip.js +3 -1
  54. package/lib/module/chip/Chip.js.map +1 -1
  55. package/lib/module/dialog/Dialog.js +16 -7
  56. package/lib/module/dialog/Dialog.js.map +1 -1
  57. package/lib/module/dialog/Dialog.web.js +12 -6
  58. package/lib/module/dialog/Dialog.web.js.map +1 -1
  59. package/lib/module/dialog/placement.js +17 -1
  60. package/lib/module/dialog/placement.js.map +1 -1
  61. package/lib/module/loading/Loading.js +8 -4
  62. package/lib/module/loading/Loading.js.map +1 -1
  63. package/lib/module/loading/Loading.web.js +8 -4
  64. package/lib/module/loading/Loading.web.js.map +1 -1
  65. package/lib/module/loading/LoadingBase.js +49 -3
  66. package/lib/module/loading/LoadingBase.js.map +1 -1
  67. package/lib/module/price-breakdown/PriceSummary.js +11 -1
  68. package/lib/module/price-breakdown/PriceSummary.js.map +1 -1
  69. package/lib/module/price-breakdown/PriceSummaryLine.js +13 -1
  70. package/lib/module/price-breakdown/PriceSummaryLine.js.map +1 -1
  71. package/lib/module/rating/Rating.js +89 -4
  72. package/lib/module/rating/Rating.js.map +1 -1
  73. package/lib/module/stepper/Stepper.js +18 -5
  74. package/lib/module/stepper/Stepper.js.map +1 -1
  75. package/lib/typescript/commonjs/breadcrumb/Breadcrumb.d.ts.map +1 -1
  76. package/lib/typescript/commonjs/breadcrumb/types.d.ts +7 -0
  77. package/lib/typescript/commonjs/breadcrumb/types.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/cart-panel/CartLine.d.ts +10 -5
  79. package/lib/typescript/commonjs/cart-panel/CartLine.d.ts.map +1 -1
  80. package/lib/typescript/commonjs/cart-panel/CartPanel.d.ts +1 -1
  81. package/lib/typescript/commonjs/cart-panel/CartPanel.d.ts.map +1 -1
  82. package/lib/typescript/commonjs/cart-panel/shared.d.ts +1 -1
  83. package/lib/typescript/commonjs/cart-panel/shared.d.ts.map +1 -1
  84. package/lib/typescript/commonjs/cart-panel/types.d.ts +18 -0
  85. package/lib/typescript/commonjs/cart-panel/types.d.ts.map +1 -1
  86. package/lib/typescript/commonjs/checkout-summary/CheckoutConfirm.d.ts +4 -1
  87. package/lib/typescript/commonjs/checkout-summary/CheckoutConfirm.d.ts.map +1 -1
  88. package/lib/typescript/commonjs/checkout-summary/types.d.ts +6 -0
  89. package/lib/typescript/commonjs/checkout-summary/types.d.ts.map +1 -1
  90. package/lib/typescript/commonjs/chip/Chip.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/chip/types.d.ts +8 -6
  92. package/lib/typescript/commonjs/chip/types.d.ts.map +1 -1
  93. package/lib/typescript/commonjs/dialog/Dialog.d.ts.map +1 -1
  94. package/lib/typescript/commonjs/dialog/Dialog.web.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/dialog/placement.d.ts +13 -2
  96. package/lib/typescript/commonjs/dialog/placement.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/dialog/types.d.ts +3 -2
  98. package/lib/typescript/commonjs/dialog/types.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/loading/Loading.d.ts.map +1 -1
  100. package/lib/typescript/commonjs/loading/Loading.web.d.ts.map +1 -1
  101. package/lib/typescript/commonjs/loading/LoadingBase.d.ts +18 -0
  102. package/lib/typescript/commonjs/loading/LoadingBase.d.ts.map +1 -1
  103. package/lib/typescript/commonjs/loading/types.d.ts +11 -0
  104. package/lib/typescript/commonjs/loading/types.d.ts.map +1 -1
  105. package/lib/typescript/commonjs/price-breakdown/PriceSummary.d.ts.map +1 -1
  106. package/lib/typescript/commonjs/price-breakdown/PriceSummaryLine.d.ts +3 -1
  107. package/lib/typescript/commonjs/price-breakdown/PriceSummaryLine.d.ts.map +1 -1
  108. package/lib/typescript/commonjs/price-breakdown/types.d.ts +12 -0
  109. package/lib/typescript/commonjs/price-breakdown/types.d.ts.map +1 -1
  110. package/lib/typescript/commonjs/rating/Rating.d.ts +3 -1
  111. package/lib/typescript/commonjs/rating/Rating.d.ts.map +1 -1
  112. package/lib/typescript/commonjs/rating/index.d.ts +1 -1
  113. package/lib/typescript/commonjs/rating/index.d.ts.map +1 -1
  114. package/lib/typescript/commonjs/rating/types.d.ts +20 -0
  115. package/lib/typescript/commonjs/rating/types.d.ts.map +1 -1
  116. package/lib/typescript/commonjs/stepper/Stepper.d.ts +1 -1
  117. package/lib/typescript/commonjs/stepper/Stepper.d.ts.map +1 -1
  118. package/lib/typescript/commonjs/stepper/types.d.ts +9 -0
  119. package/lib/typescript/commonjs/stepper/types.d.ts.map +1 -1
  120. package/lib/typescript/module/breadcrumb/Breadcrumb.d.ts.map +1 -1
  121. package/lib/typescript/module/breadcrumb/types.d.ts +7 -0
  122. package/lib/typescript/module/breadcrumb/types.d.ts.map +1 -1
  123. package/lib/typescript/module/cart-panel/CartLine.d.ts +10 -5
  124. package/lib/typescript/module/cart-panel/CartLine.d.ts.map +1 -1
  125. package/lib/typescript/module/cart-panel/CartPanel.d.ts +1 -1
  126. package/lib/typescript/module/cart-panel/CartPanel.d.ts.map +1 -1
  127. package/lib/typescript/module/cart-panel/shared.d.ts +1 -1
  128. package/lib/typescript/module/cart-panel/shared.d.ts.map +1 -1
  129. package/lib/typescript/module/cart-panel/types.d.ts +18 -0
  130. package/lib/typescript/module/cart-panel/types.d.ts.map +1 -1
  131. package/lib/typescript/module/checkout-summary/CheckoutConfirm.d.ts +4 -1
  132. package/lib/typescript/module/checkout-summary/CheckoutConfirm.d.ts.map +1 -1
  133. package/lib/typescript/module/checkout-summary/types.d.ts +6 -0
  134. package/lib/typescript/module/checkout-summary/types.d.ts.map +1 -1
  135. package/lib/typescript/module/chip/Chip.d.ts.map +1 -1
  136. package/lib/typescript/module/chip/types.d.ts +8 -6
  137. package/lib/typescript/module/chip/types.d.ts.map +1 -1
  138. package/lib/typescript/module/dialog/Dialog.d.ts.map +1 -1
  139. package/lib/typescript/module/dialog/Dialog.web.d.ts.map +1 -1
  140. package/lib/typescript/module/dialog/placement.d.ts +13 -2
  141. package/lib/typescript/module/dialog/placement.d.ts.map +1 -1
  142. package/lib/typescript/module/dialog/types.d.ts +3 -2
  143. package/lib/typescript/module/dialog/types.d.ts.map +1 -1
  144. package/lib/typescript/module/loading/Loading.d.ts.map +1 -1
  145. package/lib/typescript/module/loading/Loading.web.d.ts.map +1 -1
  146. package/lib/typescript/module/loading/LoadingBase.d.ts +18 -0
  147. package/lib/typescript/module/loading/LoadingBase.d.ts.map +1 -1
  148. package/lib/typescript/module/loading/types.d.ts +11 -0
  149. package/lib/typescript/module/loading/types.d.ts.map +1 -1
  150. package/lib/typescript/module/price-breakdown/PriceSummary.d.ts.map +1 -1
  151. package/lib/typescript/module/price-breakdown/PriceSummaryLine.d.ts +3 -1
  152. package/lib/typescript/module/price-breakdown/PriceSummaryLine.d.ts.map +1 -1
  153. package/lib/typescript/module/price-breakdown/types.d.ts +12 -0
  154. package/lib/typescript/module/price-breakdown/types.d.ts.map +1 -1
  155. package/lib/typescript/module/rating/Rating.d.ts +3 -1
  156. package/lib/typescript/module/rating/Rating.d.ts.map +1 -1
  157. package/lib/typescript/module/rating/index.d.ts +1 -1
  158. package/lib/typescript/module/rating/index.d.ts.map +1 -1
  159. package/lib/typescript/module/rating/types.d.ts +20 -0
  160. package/lib/typescript/module/rating/types.d.ts.map +1 -1
  161. package/lib/typescript/module/stepper/Stepper.d.ts +1 -1
  162. package/lib/typescript/module/stepper/Stepper.d.ts.map +1 -1
  163. package/lib/typescript/module/stepper/types.d.ts +9 -0
  164. package/lib/typescript/module/stepper/types.d.ts.map +1 -1
  165. package/package.json +1 -1
  166. package/src/breadcrumb/Breadcrumb.tsx +29 -4
  167. package/src/breadcrumb/types.ts +7 -0
  168. package/src/cart-panel/CartLine.tsx +30 -7
  169. package/src/cart-panel/CartPanel.tsx +3 -0
  170. package/src/cart-panel/shared.ts +11 -1
  171. package/src/cart-panel/types.ts +18 -0
  172. package/src/checkout-summary/CheckoutConfirm.tsx +9 -0
  173. package/src/checkout-summary/types.ts +6 -0
  174. package/src/chip/Chip.tsx +3 -1
  175. package/src/chip/types.ts +8 -6
  176. package/src/dialog/Dialog.tsx +19 -8
  177. package/src/dialog/Dialog.web.tsx +15 -7
  178. package/src/dialog/placement.ts +19 -2
  179. package/src/dialog/types.ts +3 -2
  180. package/src/loading/Loading.tsx +6 -2
  181. package/src/loading/Loading.web.tsx +6 -2
  182. package/src/loading/LoadingBase.tsx +55 -5
  183. package/src/loading/types.ts +11 -0
  184. package/src/price-breakdown/PriceSummary.tsx +10 -0
  185. package/src/price-breakdown/PriceSummaryLine.tsx +12 -0
  186. package/src/price-breakdown/types.ts +12 -0
  187. package/src/rating/Rating.tsx +87 -3
  188. package/src/rating/index.ts +1 -0
  189. package/src/rating/types.ts +21 -0
  190. package/src/stepper/Stepper.tsx +18 -5
  191. package/src/stepper/types.ts +9 -0
@@ -82,6 +82,7 @@ function PriceSummaryComponent({
82
82
  label={line.label}
83
83
  sublabel={line.sublabel}
84
84
  amount={line.amount}
85
+ secondaryAmount={line.secondaryAmount}
85
86
  tone={line.tone}
86
87
  state={line.state}
87
88
  info={line.info}
@@ -137,6 +138,15 @@ function PriceSummaryComponent({
137
138
  >
138
139
  {total.amount ?? pendingPlaceholder}
139
140
  </Text>
141
+ {total.secondaryAmount ? (
142
+ <Text
143
+ variant="body-2-regular"
144
+ testID={testID ? `${testID}-total-secondary-amount` : undefined}
145
+ style={{ color: paint.textSecondary, fontVariant: ['tabular-nums'], textAlign: 'right' }}
146
+ >
147
+ {total.secondaryAmount}
148
+ </Text>
149
+ ) : null}
140
150
  {totalCaveat ? (
141
151
  <Text
142
152
  variant="caption-1-regular"
@@ -27,6 +27,8 @@ import type { PriceSummaryLineProps } from './types';
27
27
  * state "Estimated" / "Pending" in caption-1 under the amount, so the
28
28
  * caveat travels with the NUMBER rather than with the label; the
29
29
  * row keeps its own reading order for a screen reader
30
+ * secondary `secondaryAmount` (a second currency) in body-2-regular
31
+ * secondary under the amount, tabular, right-aligned
30
32
  * info a 32 round glyph after the label, opening a `Popover`
31
33
  * height 32 minimum, whether or not the line has the info affordance
32
34
  *
@@ -38,6 +40,7 @@ function PriceSummaryLineComponent({
38
40
  label,
39
41
  sublabel,
40
42
  amount,
43
+ secondaryAmount,
41
44
  tone = 'default',
42
45
  state = 'final',
43
46
  info,
@@ -120,6 +123,15 @@ function PriceSummaryLineComponent({
120
123
  >
121
124
  {amount ?? pendingPlaceholder}
122
125
  </Text>
126
+ {secondaryAmount ? (
127
+ <Text
128
+ variant="body-2-regular"
129
+ testID={testID ? `${testID}-secondary-amount` : undefined}
130
+ style={{ color: paint.textSecondary, fontVariant: ['tabular-nums'], textAlign: 'right' }}
131
+ >
132
+ {secondaryAmount}
133
+ </Text>
134
+ ) : null}
123
135
  {caveat ? (
124
136
  <Text
125
137
  variant="caption-1-regular"
@@ -32,6 +32,12 @@ export interface PriceLine {
32
32
  * app owns currency, locale and the minus sign. Omit it for a `pending` line.
33
33
  */
34
34
  amount?: string;
35
+ /**
36
+ * A second, PRE-FORMATTED rendering of the same amount, drawn de-emphasised
37
+ * under it — the buyer's currency beside the shop's: `"≈ 12,00 €"`. Like
38
+ * `ListingPriceLine.secondary`, it is drawn as given; nothing converts.
39
+ */
40
+ secondaryAmount?: string;
35
41
  /** Default `default`. */
36
42
  tone?: PriceLineTone;
37
43
  /** Default `final`. */
@@ -54,6 +60,12 @@ export interface PriceTotal {
54
60
  label: string;
55
61
  /** PRE-FORMATTED, like every other amount here. */
56
62
  amount?: string;
63
+ /**
64
+ * A second, PRE-FORMATTED rendering of the same amount, drawn de-emphasised
65
+ * under it — the buyer's currency beside the shop's: `"≈ 12,00 €"`. Like
66
+ * `ListingPriceLine.secondary`, it is drawn as given; nothing converts.
67
+ */
68
+ secondaryAmount?: string;
57
69
  /** A quieter line under the total — "Includes taxes". */
58
70
  note?: string;
59
71
  /** Default `final`. */
@@ -2,6 +2,7 @@ import React, { memo } from 'react';
2
2
  import { Platform, View } from 'react-native';
3
3
 
4
4
  import { RiStarFill } from '../icons/remix/RiStarFill';
5
+ import { useDirectionProps } from '../hooks/use-is-rtl';
5
6
  import { useTheme } from '../theme/use-theme';
6
7
  import { Text } from '../typography';
7
8
  import type { TypeScaleVariant } from '../typography/scale';
@@ -21,6 +22,16 @@ import type { RatingProps, RatingSize } from './types';
21
22
  * reviews") on the row — `role="img"` on web, where a plain `div` with an
22
23
  * `aria-label` is not announced, and one `accessible` element on native — so a
23
24
  * screen reader never reads "star, 4.92, open paren, 128".
25
+ *
26
+ * `variant="stars"` draws five stars before the value instead of one, 2px
27
+ * apart, each filled to its share of the value: the unfilled star in
28
+ * `emptyStarColor` (border), and over it the filled star clipped to the
29
+ * fraction. The clip is anchored with a LOGICAL inset (`insetInlineStart`) so a
30
+ * right-to-left row fills from the right — the root carries
31
+ * `useDirectionProps()` for react-native-web to resolve it.
32
+ *
33
+ * `color` / `starColor` / `countColor` recolour it for a surface the theme's
34
+ * text pair cannot sit on (a brand-coloured hero).
24
35
  */
25
36
 
26
37
  const SIZE_CONFIG: Record<RatingSize, { star: number; value: TypeScaleVariant; count: TypeScaleVariant }> = {
@@ -37,6 +48,59 @@ export function formatRatingValue(value: number | string): string {
37
48
 
38
49
  const IS_WEB = Platform.OS === 'web';
39
50
 
51
+ /** The number of stars in the `stars` variant. */
52
+ const STAR_COUNT = 5;
53
+ const STAR_GAP = 2;
54
+
55
+ /** How much of star `index` (0-based) a rating fills, `0..1`. */
56
+ export function starFill(rating: number, index: number): number {
57
+ if (!Number.isFinite(rating)) return 0;
58
+ return Math.min(1, Math.max(0, rating - index));
59
+ }
60
+
61
+ function StarRow({
62
+ rating,
63
+ size,
64
+ fill,
65
+ empty,
66
+ testID,
67
+ }: {
68
+ rating: number;
69
+ size: number;
70
+ fill: string;
71
+ empty: string;
72
+ testID?: string;
73
+ }) {
74
+ return (
75
+ <View style={{ flexDirection: 'row', alignItems: 'center', gap: STAR_GAP }} testID={testID}>
76
+ {Array.from({ length: STAR_COUNT }, (_, index) => {
77
+ const share = starFill(rating, index);
78
+ return (
79
+ <View key={index} style={{ width: size, height: size }}>
80
+ <RiStarFill width={size} height={size} fill={empty} />
81
+ {share > 0 ? (
82
+ <View
83
+ testID={testID ? `${testID}-${index}` : undefined}
84
+ style={{
85
+ position: 'absolute',
86
+ top: 0,
87
+ bottom: 0,
88
+ insetInlineStart: 0,
89
+ width: size * share,
90
+ overflow: 'hidden',
91
+ alignItems: 'flex-start',
92
+ }}
93
+ >
94
+ <RiStarFill width={size} height={size} fill={fill} />
95
+ </View>
96
+ ) : null}
97
+ </View>
98
+ );
99
+ })}
100
+ </View>
101
+ );
102
+ }
103
+
40
104
  function RatingComponent({
41
105
  value,
42
106
  count,
@@ -44,11 +108,20 @@ function RatingComponent({
44
108
  reviewsLabel = 'reviews',
45
109
  newLabel = 'New',
46
110
  size = 'medium',
111
+ variant = 'compact',
112
+ color,
113
+ starColor,
114
+ countColor,
115
+ emptyStarColor,
47
116
  accessibilityLabel,
48
117
  style,
49
118
  testID,
50
119
  }: RatingProps) {
51
120
  const theme = useTheme();
121
+ const directionProps = useDirectionProps();
122
+ const textColor = color ?? theme.colors.text;
123
+ const starPaint = starColor ?? textColor;
124
+ const countPaint = countColor ?? color ?? theme.colors.textSecondary;
52
125
  const config = SIZE_CONFIG[size];
53
126
  const rated = value != null && value !== '';
54
127
  const shownValue = rated ? formatRatingValue(value) : newLabel;
@@ -66,18 +139,29 @@ function RatingComponent({
66
139
 
67
140
  return (
68
141
  <View
142
+ {...directionProps}
69
143
  testID={testID}
70
144
  accessible
71
145
  accessibilityLabel={name}
72
146
  {...(IS_WEB ? { role: 'img' as const } : null)}
73
147
  style={[{ flexDirection: 'row', alignItems: 'center', gap: 4 }, style]}
74
148
  >
75
- <RiStarFill width={config.star} height={config.star} fill={theme.colors.text} />
76
- <Text variant={config.value} style={{ color: theme.colors.text, fontVariant: ['tabular-nums'] }}>
149
+ {variant === 'stars' && rated ? (
150
+ <StarRow
151
+ rating={typeof value === 'number' ? value : Number(value)}
152
+ size={config.star}
153
+ fill={starPaint}
154
+ empty={emptyStarColor ?? theme.colors.border}
155
+ testID={testID ? `${testID}-stars` : undefined}
156
+ />
157
+ ) : variant === 'stars' ? null : (
158
+ <RiStarFill width={config.star} height={config.star} fill={starPaint} />
159
+ )}
160
+ <Text variant={config.value} style={{ color: textColor, fontVariant: ['tabular-nums'] }}>
77
161
  {shownValue}
78
162
  </Text>
79
163
  {countText != null && (
80
- <Text variant={config.count} style={{ color: theme.colors.textSecondary }}>
164
+ <Text variant={config.count} style={{ color: countPaint }}>
81
165
  {countText}
82
166
  </Text>
83
167
  )}
@@ -8,4 +8,5 @@ export type {
8
8
  RatingInputSize,
9
9
  RatingProps,
10
10
  RatingSize,
11
+ RatingVariant,
11
12
  } from './types';
@@ -6,6 +6,13 @@ export type RatingSize = 'small' | 'medium';
6
6
  /** `parenthesis` draws "(128)"; `reviews` draws "· 128 reviews". */
7
7
  export type RatingCountStyle = 'parenthesis' | 'reviews';
8
8
 
9
+ /**
10
+ * `compact` (default) is one star and the value; `stars` a full row of `max`
11
+ * stars (five) filled to the value — fractionally, so 4.3 fills the fifth star
12
+ * 30% — before the value.
13
+ */
14
+ export type RatingVariant = 'compact' | 'stars';
15
+
9
16
  export interface RatingProps {
10
17
  /**
11
18
  * The rating on a 5 scale. A number is drawn with up to two decimals (4.9,
@@ -23,6 +30,20 @@ export interface RatingProps {
23
30
  newLabel?: string;
24
31
  /** Default `medium`. */
25
32
  size?: RatingSize;
33
+ /** Default `compact` — one star. `stars` draws a row of five, filled fractionally. */
34
+ variant?: RatingVariant;
35
+ /**
36
+ * Paints the star(s) and the value — and the count, unless `countColor` is
37
+ * given — e.g. white over a brand-coloured hero, where text-primary and
38
+ * text-secondary are illegible. Default: text-primary (count text-secondary).
39
+ */
40
+ color?: string;
41
+ /** The filled star(s) alone, e.g. a gold star beside neutral text. Default `color`. */
42
+ starColor?: string;
43
+ /** The count alone. Default `color` when given, else text-secondary. */
44
+ countColor?: string;
45
+ /** `stars` variant: the unfilled part of each star. Default the theme's border colour. */
46
+ emptyStarColor?: string;
26
47
  /**
27
48
  * Overrides the composed accessible name ("Rated 4.92 out of 5, 128
28
49
  * reviews"), which is English — pass a translated sentence here.
@@ -5,6 +5,7 @@ import { Button } from '../button';
5
5
  import { webDataSet } from '../styles/web-data';
6
6
  import { useAccessibleNameWarning } from '../hooks/use-accessible-name-warning';
7
7
  import { RiAddLine } from '../icons/remix/RiAddLine';
8
+ import { RiDeleteBinLine } from '../icons/remix/RiDeleteBinLine';
8
9
  import { RiSubtractLine } from '../icons/remix/RiSubtractLine';
9
10
  import { useRingOffsetStyle } from '../styles/surface-levels';
10
11
  import { interactiveWebCss, useInteractiveWebCss } from '../styles/interactive-web-css';
@@ -33,6 +34,14 @@ import { useFieldMembership } from '../field/membership';
33
34
  * the tab order, being pointer affordances for the same action. On native it
34
35
  * answers the `increment`/`decrement` accessibility actions (VoiceOver swipe
35
36
  * up/down, TalkBack volume keys).
37
+ *
38
+ * REMOVE AT THE FLOOR is opt-in (`onRemove`). With it, the `−` button at `min`
39
+ * turns into a trash button named `removeLabel` that calls `onRemove` — a
40
+ * basket line that goes from 1 to gone. Without it the button disables at
41
+ * `min`, as it always has. The keyboard and the accessibility actions on the
42
+ * value never remove: an arrow key or a volume-key swipe that deletes is a
43
+ * destructive action behind an arithmetic one. So that a keyboard user can
44
+ * still reach it, the button joins the tab order while it is the remove control.
36
45
  */
37
46
 
38
47
  const IS_WEB = Platform.OS === 'web';
@@ -83,6 +92,8 @@ function StepperComponent({
83
92
  accessibilityLabel,
84
93
  decrementLabel = 'Decrease',
85
94
  incrementLabel = 'Increase',
95
+ onRemove,
96
+ removeLabel = 'Remove',
86
97
  style,
87
98
  testID,
88
99
  }: StepperProps) {
@@ -99,6 +110,8 @@ function StepperComponent({
99
110
 
100
111
  const canDecrement = !disabled && value > min;
101
112
  const canIncrement = !disabled && (max === undefined || value < max);
113
+ // At the floor with `onRemove`, the decrement slot IS the remove control.
114
+ const removes = onRemove !== undefined && !disabled && value <= min;
102
115
 
103
116
  const change = useCallback(
104
117
  (target: number) => {
@@ -180,11 +193,11 @@ function StepperComponent({
180
193
  variant="secondary"
181
194
  size={config.button}
182
195
  iconOnly
183
- icon={RiSubtractLine}
184
- onPress={decrement}
185
- disabled={!canDecrement}
186
- accessibilityLabel={decrementLabel}
187
- tabIndex={-1}
196
+ icon={removes ? RiDeleteBinLine : RiSubtractLine}
197
+ onPress={removes ? onRemove : decrement}
198
+ disabled={!(canDecrement || removes)}
199
+ accessibilityLabel={removes ? removeLabel : decrementLabel}
200
+ tabIndex={removes ? 0 : -1}
188
201
  testID={testID ? `${testID}-decrement` : undefined}
189
202
  />
190
203
  <View
@@ -38,6 +38,15 @@ export interface StepperProps {
38
38
  decrementLabel?: string;
39
39
  /** Name of the `+` button. Default `"Increase"`. */
40
40
  incrementLabel?: string;
41
+ /**
42
+ * Opt-in remove at the floor: at `min` the `−` button becomes a trash button
43
+ * that calls this instead of disabling (a basket line going from 1 to gone).
44
+ * Only the button removes — the keyboard and accessibility actions on the
45
+ * value still stop at `min`. Without it the stepper floors at `min`.
46
+ */
47
+ onRemove?: () => void;
48
+ /** Name of the trash button drawn at `min` with `onRemove`. Default `"Remove"`. */
49
+ removeLabel?: string;
41
50
  style?: StyleProp<ViewStyle>;
42
51
  testID?: string;
43
52
  }