@delacour/react-native-ui 0.4.0-alpha.20261020155447 → 0.4.1-alpha.20261020200151

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delacour/react-native-ui",
3
- "version": "0.4.0-alpha.20261020155447",
3
+ "version": "0.4.1-alpha.20261020200151",
4
4
  "description": "React Native UI components — Uniwind, Reanimated, Gesture Handler",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -181,7 +181,7 @@
181
181
  "expo-blur": ">=15",
182
182
  "@maplibre/maplibre-react-native": "^11.5.0",
183
183
  "@central-icons-react-native/round-outlined-radius-1-stroke-1.5": "^1.1",
184
- "@delacour/react-native-bottom-sheet": "0.2.0-alpha.20261020155447",
184
+ "@delacour/react-native-bottom-sheet": ">=0.2.0",
185
185
  "@delacour/react-native-charts": ">=0.1.0",
186
186
  "@legendapp/list": ">=3.3",
187
187
  "@shopify/react-native-skia": ">=2",
@@ -75,13 +75,18 @@ sheets.
75
75
 
76
76
  ## Drawing
77
77
 
78
- - **A day is three layers:** `band` (absolute, edge to edge, behind), `dayBase` (the circle) and
79
- the label. The band spans the full cell so adjacent days join into one strip.
80
- - **The band ends at the centre of each end cell, and the circle is the cap.** Start runs from its
81
- cell's centre to the right edge, end from the left edge to its centre, middle spans the cell; none
82
- is rounded. A cell is wider than its circle, so the first version — a band rounded across the
83
- whole end cell — showed a crescent of band beside the circle at both ends. A lone `only` day
84
- draws no band. At a week's edge the strip simply stops and resumes on the next row.
78
+ - **A day is three layers:** the band (`bandTrack`, absolute, edge to edge, behind), `dayBase`
79
+ (the circle) and the label. The band spans the full cell so adjacent days join into one strip.
80
+ - **An end's band runs under the whole circle, capped at the circle's radius.** `bandTrack` clips
81
+ to the cell and centres `bandAnchor`, a box `w-input-*` wide — the width `size-input-*` gives the
82
+ circle. The band hangs off the anchor: start from its left edge (`rounded-l-full`) out past the
83
+ cell's right, end the mirror, middle past both edges and square; the clip trims each to the
84
+ cell. Three earlier versions failed: a band rounded across the whole end cell showed a crescent
85
+ beside the circle; one stopped at the cell's centre put half a translucent `secondary` circle
86
+ over band and half over the page — a two-tone circle; and one sized by `basis-input-*` did not
87
+ follow the house style's runtime retune of the input scale, so at `lg` the cap slid ~6pt off
88
+ the circle. A lone `only` day draws no band. At a week's edge the strip simply stops and
89
+ resumes on the next row.
85
90
  - **Today is a ring (`border-ring`) and `text-primary`, never a fill**, so it cannot be mistaken
86
91
  for a selection.
87
92
  - **Variant:** `primary` fills `bg-primary`, band `bg-primary/15`; `secondary` fills
@@ -95,7 +95,13 @@ export function CalendarDay({ date, children, className }: CalendarDayProps): Re
95
95
  onPress={handlePress}
96
96
  testID={resolvePartTestID(calendar.testID, `day-${serialiseDate(date)}`)}
97
97
  >
98
- {showsBand ? <View className={slots.band()} /> : null}
98
+ {showsBand ? (
99
+ <View className={slots.bandTrack()}>
100
+ <View className={slots.bandAnchor()}>
101
+ <View className={slots.band()} />
102
+ </View>
103
+ </View>
104
+ ) : null}
99
105
  <View className={slots.dayBase()}>{content}</View>
100
106
  </Pressable>
101
107
  );
@@ -90,6 +90,8 @@ describe("text colour stays on the text", () => {
90
90
  "week",
91
91
  "cell",
92
92
  "dayBase",
93
+ "bandTrack",
94
+ "bandAnchor",
93
95
  "band",
94
96
  "pickerGrid",
95
97
  "pickerItem",
@@ -111,33 +113,52 @@ describe("text colour stays on the text", () => {
111
113
  });
112
114
 
113
115
  describe("range band geometry", () => {
114
- const bandOf = (rangeRole: CalendarRangeRole) => calendarVariants({ rangeRole }).band();
116
+ const slotsOf = (rangeRole: CalendarRangeRole) => calendarVariants({ rangeRole });
117
+ const bandOf = (rangeRole: CalendarRangeRole) => slotsOf(rangeRole).band();
115
118
 
116
119
  // The cell is wider than the circle, so a band end rounded across the whole cell pokes out
117
- // beside the circle. Each end instead stops at its cell's centre, under the circle.
118
- test("start runs from the cell's centre to its right edge", () => {
119
- expect(bandOf("start")).toMatch(/\bleft-1\/2\b/);
120
- expect(bandOf("start")).toMatch(/\bright-0\b/);
120
+ // beside the circle. Each end instead runs under the whole circle with a cap of its radius, so a
121
+ // translucent circle lies over band everywhere and never shows a two-tone half.
122
+ test("the track clips to the cell and centres the anchor", () => {
123
+ const track = slotsOf("start").bandTrack();
124
+ expect(track).toMatch(/\babsolute\b/);
125
+ expect(track).toMatch(/\binset-0\b/);
126
+ expect(track).toMatch(/\bitems-center\b/);
127
+ expect(track).toMatch(/\boverflow-hidden\b/);
128
+ });
129
+
130
+ // `w-input-*` is the width `size-input-*` sets, so the anchor tracks the circle through a
131
+ // runtime retune of the input scale; `basis-input-*` did not, and the cap slid off the circle.
132
+ test("the anchor is exactly as wide as the circle", () => {
133
+ for (const size of ["sm", "md", "lg"] as const) {
134
+ const slots = calendarVariants({ rangeRole: "start", size });
135
+ expect(slots.bandAnchor()).toContain(`w-input-${size}`);
136
+ expect(slots.dayBase()).toContain(`size-input-${size}`);
137
+ }
121
138
  });
122
139
 
123
- test("end runs from the cell's left edge to its centre", () => {
124
- expect(bandOf("end")).toMatch(/\bleft-0\b/);
125
- expect(bandOf("end")).toMatch(/\bright-1\/2\b/);
140
+ test("start runs from the circle's left edge out past the cell, capped on the left", () => {
141
+ expect(bandOf("start")).toMatch(/(?:^|\s)left-0\b/);
142
+ expect(bandOf("start")).toMatch(/-right-\[\d+px\]/);
143
+ expect(bandOf("start")).toMatch(/\brounded-l-full\b/);
144
+ expect(bandOf("start")).not.toMatch(/\brounded-r-full\b/);
126
145
  });
127
146
 
128
- test("middle spans the whole cell, square", () => {
129
- expect(bandOf("middle")).toMatch(/\binset-x-0\b/);
130
- expect(bandOf("middle")).not.toMatch(/rounded-(?:l-|r-)?full/);
147
+ test("end mirrors start", () => {
148
+ expect(bandOf("end")).toMatch(/(?:^|\s)right-0\b/);
149
+ expect(bandOf("end")).toMatch(/-left-\[\d+px\]/);
150
+ expect(bandOf("end")).toMatch(/\brounded-r-full\b/);
151
+ expect(bandOf("end")).not.toMatch(/\brounded-l-full\b/);
131
152
  });
132
153
 
133
- test("no band end is rounded — the circle is the cap", () => {
134
- for (const rangeRole of ["start", "middle", "end"] as const) {
135
- expect(bandOf(rangeRole)).not.toMatch(/rounded-(?:l-|r-)?full/);
136
- }
154
+ test("middle runs past both edges, square", () => {
155
+ expect(bandOf("middle")).toMatch(/-left-\[\d+px\]/);
156
+ expect(bandOf("middle")).toMatch(/-right-\[\d+px\]/);
157
+ expect(bandOf("middle")).not.toMatch(/rounded-(?:l-|r-)?full/);
137
158
  });
138
159
 
139
160
  test("a lone day draws no band", () => {
140
- expect(bandOf("only")).toContain("hidden");
161
+ expect(slotsOf("only").bandTrack()).toContain("hidden");
141
162
  });
142
163
 
143
164
  test("a selected day is a circle, never square", () => {
@@ -54,12 +54,14 @@ export const CALENDAR_PAGE_VELOCITY = 500;
54
54
  * whole cell so neighbouring days join into one continuous strip — a gap between columns would
55
55
  * break it into beads.
56
56
  *
57
- * **The band ends under the circle, never beside it.** Start runs from its cell's centre to the
58
- * right edge, end from the left edge to its centre, and every day between spans the whole cell
59
- * square — including at a week's edge, where the strip simply stops and picks up again on the
60
- * next row. A cell is wider than its circle, so a band rounded across the whole end cell poked a
61
- * crescent out beside the circle; stopping at the centre lets the circle itself be the cap. A
62
- * one-day range draws no band at all.
57
+ * **The band ends under the whole circle, never beside it.** `bandTrack` clips to the cell and
58
+ * centres `bandAnchor`, a box as wide as the circle — `w-input-*`, the width `size-input-*` sets,
59
+ * so a runtime retune of the input scale moves both. The band hangs off the anchor: start from
60
+ * its left edge, capped at the circle's radius, out past the cell's right; end the mirror; every
61
+ * day between past both edges, so the clip leaves it spanning the whole cell square — including at a week's edge, where the strip simply stops and
62
+ * picks up again on the next row. A band rounded across the whole end cell poked a crescent out
63
+ * beside the circle; one stopped at the cell's centre left half a translucent `secondary` circle
64
+ * over band and half over the page, a two-tone circle. A one-day range draws no band at all.
63
65
  *
64
66
  * **The cell steps on the input scale** — `h-input-*` and `text-input-*` — so a calendar inline
65
67
  * beside a field shares its density, and a retune of the field retunes the calendar.
@@ -91,7 +93,11 @@ export const calendarVariants = tv({
91
93
  week: "flex-row",
92
94
  /** One day's column share, and the height of its touch target. */
93
95
  cell: "flex-1 items-center justify-center",
94
- /** The strip behind a day inside a range. */
96
+ /** The cell-sized clip behind a day inside a range, centring the band's anchor. */
97
+ bandTrack: "absolute inset-0 items-center overflow-hidden",
98
+ /** A circle-wide box where the circle sits, that the band hangs off. */
99
+ bandAnchor: "h-full",
100
+ /** The strip behind a day inside a range, run past the cell and clipped by the track. */
95
101
  band: "absolute inset-y-0",
96
102
  /** The circle a selection fills and today rings. */
97
103
  dayBase: "items-center justify-center rounded-full border border-transparent",
@@ -113,6 +119,7 @@ export const calendarVariants = tv({
113
119
  sm: {
114
120
  captionText: "text-input-sm",
115
121
  cell: "h-input-sm",
122
+ bandAnchor: "w-input-sm",
116
123
  dayBase: "size-input-sm",
117
124
  dayLabel: "text-input-sm",
118
125
  pickerItem: "h-input-sm",
@@ -121,6 +128,7 @@ export const calendarVariants = tv({
121
128
  md: {
122
129
  captionText: "text-input-md",
123
130
  cell: "h-input-md",
131
+ bandAnchor: "w-input-md",
124
132
  dayBase: "size-input-md",
125
133
  dayLabel: "text-input-md",
126
134
  pickerItem: "h-input-md",
@@ -129,6 +137,7 @@ export const calendarVariants = tv({
129
137
  lg: {
130
138
  captionText: "text-input-lg",
131
139
  cell: "h-input-lg",
140
+ bandAnchor: "w-input-lg",
132
141
  dayBase: "size-input-lg",
133
142
  dayLabel: "text-input-lg",
134
143
  pickerItem: "h-input-lg",
@@ -141,11 +150,11 @@ export const calendarVariants = tv({
141
150
  band: { dayBase: "bg-transparent" },
142
151
  },
143
152
  rangeRole: {
144
- none: { band: "hidden" },
145
- start: { band: "left-1/2 right-0" },
146
- middle: { band: "inset-x-0" },
147
- end: { band: "left-0 right-1/2" },
148
- only: { band: "hidden" },
153
+ none: { bandTrack: "hidden" },
154
+ start: { band: "-right-[1000px] left-0 rounded-l-full" },
155
+ middle: { band: "-left-[1000px] -right-[1000px]" },
156
+ end: { band: "-left-[1000px] right-0 rounded-r-full" },
157
+ only: { bandTrack: "hidden" },
149
158
  },
150
159
  isToday: { true: {}, false: {} },
151
160
  isOutside: { true: {}, false: {} },