@gnome-ui/react-native 1.11.0 → 1.13.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 +310 -0
- package/dist/components/Calendar/Calendar.d.ts +95 -0
- package/dist/components/Calendar/CalendarBase.d.ts +83 -0
- package/dist/components/Calendar/calendarUtils.d.ts +66 -0
- package/dist/components/Calendar/index.d.ts +2 -0
- package/dist/components/CalendarRange/CalendarRange.d.ts +75 -0
- package/dist/components/CalendarRange/index.d.ts +2 -0
- package/dist/components/CalendarRange/rangeUtils.d.ts +31 -0
- package/dist/components/DatePicker/DatePicker.d.ts +75 -0
- package/dist/components/DatePicker/index.d.ts +2 -0
- package/dist/components/LinkedGroup/LinkedGroup.d.ts +50 -0
- package/dist/components/LinkedGroup/index.d.ts +2 -0
- package/dist/components/NavigationSplitView/NavigationSplitView.d.ts +69 -0
- package/dist/components/NavigationSplitView/index.d.ts +2 -0
- package/dist/components/RatingStars/RatingStars.d.ts +48 -0
- package/dist/components/RatingStars/index.d.ts +2 -0
- package/dist/components/TimePicker/TimeFields.d.ts +36 -0
- package/dist/components/TimePicker/TimePicker.d.ts +55 -0
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/timeUtils.d.ts +36 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2346 -1535
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -294,6 +294,43 @@ announces the same thing, mirroring the web version's `aria-label` on its
|
|
|
294
294
|
icon span. Unlike the web `Link`, RN has no tab concept, so `external` is
|
|
295
295
|
purely presentational — `href` always opens the same way regardless.
|
|
296
296
|
|
|
297
|
+
### LinkedGroup
|
|
298
|
+
|
|
299
|
+
```tsx
|
|
300
|
+
import { Button, LinkedGroup } from '@gnome-ui/react-native';
|
|
301
|
+
|
|
302
|
+
<LinkedGroup>
|
|
303
|
+
<Button>Cut</Button>
|
|
304
|
+
<Button>Copy</Button>
|
|
305
|
+
<Button>Paste</Button>
|
|
306
|
+
</LinkedGroup>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Renders children as a single visually-connected unit with no gap and
|
|
310
|
+
merged borders — the canonical GNOME pattern for button groups and
|
|
311
|
+
segmented inputs. Mirrors `@gnome-ui/react`'s `LinkedGroup`, itself
|
|
312
|
+
mirroring the libadwaita `.linked` style class.
|
|
313
|
+
|
|
314
|
+
The web version reaches every child's border-radius via a CSS `> *`
|
|
315
|
+
universal child selector — RN has no equivalent way for a parent `View`
|
|
316
|
+
to reach into an arbitrary child's own internally-computed styles.
|
|
317
|
+
Reimagined as the same `cloneElement`-onto-children technique
|
|
318
|
+
`Popover`/`Tooltip` already use on their own trigger: each child gets a
|
|
319
|
+
computed corner-radius/negative-margin override merged onto whatever
|
|
320
|
+
`style` it already has, generalizing the "zero the shared inner corners,
|
|
321
|
+
keep `theme.radiusMd` on the outer ones, overlap by 1 dp to collapse the
|
|
322
|
+
shared border" recipe `SplitButton` already proved for its own fixed
|
|
323
|
+
two-piece connected border. This only works because every component in
|
|
324
|
+
this package already merges a passed-in `style` prop last — the same
|
|
325
|
+
assumption `Popover`'s own trigger-cloning already depends on, so any
|
|
326
|
+
custom child passed to `LinkedGroup` needs to follow that same
|
|
327
|
+
convention.
|
|
328
|
+
|
|
329
|
+
The web CSS also raises a hovered/focused child's `z-index` so its own
|
|
330
|
+
border isn't visually covered by the next sibling's overlapping edge —
|
|
331
|
+
dropped here: RN is touch-first (no `:hover`), and no component in this
|
|
332
|
+
package currently renders an escaping focus ring that overlap could clip.
|
|
333
|
+
|
|
297
334
|
### TextField
|
|
298
335
|
|
|
299
336
|
```tsx
|
|
@@ -2510,6 +2547,40 @@ panel-height positioning almost verbatim, and gives each option row a
|
|
|
2510
2547
|
leading checkbox-square visual instead of `Dropdown`'s single trailing
|
|
2511
2548
|
checkmark.
|
|
2512
2549
|
|
|
2550
|
+
### NavigationSplitView
|
|
2551
|
+
|
|
2552
|
+
```tsx
|
|
2553
|
+
import { NavigationSplitView } from '@gnome-ui/react-native';
|
|
2554
|
+
|
|
2555
|
+
const [showContent, setShowContent] = useState(false);
|
|
2556
|
+
|
|
2557
|
+
<NavigationSplitView
|
|
2558
|
+
showContent={showContent}
|
|
2559
|
+
sidebar={<MailList onSelect={() => setShowContent(true)} />}
|
|
2560
|
+
content={<MailDetail onBack={() => setShowContent(false)} />}
|
|
2561
|
+
/>;
|
|
2562
|
+
```
|
|
2563
|
+
|
|
2564
|
+
Two-pane sidebar + content layout following the Adwaita
|
|
2565
|
+
`AdwNavigationSplitView` pattern — mirrors `@gnome-ui/react`'s
|
|
2566
|
+
`NavigationSplitView`. On wide screens (`useBreakpoint().isNarrow ===
|
|
2567
|
+
false`, > 400 dp) both panes render side by side, separated by a
|
|
2568
|
+
`Separator`. On narrow screens only one pane shows at a time; `showContent`
|
|
2569
|
+
switches between the sidebar list and the detail view.
|
|
2570
|
+
|
|
2571
|
+
The web version's `clamp(min, fraction * 100%, max)` sidebar width has no
|
|
2572
|
+
RN equivalent, so the container measures its own width via `onLayout` and
|
|
2573
|
+
the same clamp is computed in JS. Narrow-mode pane switching is animated
|
|
2574
|
+
(`translateX`) rather than an instant `display: 'none'` swap like
|
|
2575
|
+
`TabPanel` — both panes stay laid out and absolutely positioned, sliding
|
|
2576
|
+
via one shared `Animated.Value`, since RN `transform` has no
|
|
2577
|
+
percentage-of-self units to use a bare `-100%`/`100%` the way the web CSS
|
|
2578
|
+
does. The web's `inert` attribute (removes the hidden pane from the a11y
|
|
2579
|
+
tree and tab order while it stays mounted off-screen) has no single RN
|
|
2580
|
+
equivalent — reproduced with `accessibilityElementsHidden` +
|
|
2581
|
+
`importantForAccessibility="no-hide-descendants"` plus `pointerEvents="none"`
|
|
2582
|
+
so the off-screen pane can't intercept touches meant for the visible one.
|
|
2583
|
+
|
|
2513
2584
|
### FilterableMultiSelectDropdown
|
|
2514
2585
|
|
|
2515
2586
|
```tsx
|
|
@@ -2589,6 +2660,35 @@ two independent `accessibilityRole="adjustable"` elements (one per thumb),
|
|
|
2589
2660
|
the same VoiceOver/TalkBack increment/decrement analog `Slider` already
|
|
2590
2661
|
established.
|
|
2591
2662
|
|
|
2663
|
+
### RatingStars
|
|
2664
|
+
|
|
2665
|
+
```tsx
|
|
2666
|
+
import { RatingStars } from '@gnome-ui/react-native';
|
|
2667
|
+
|
|
2668
|
+
// Read-only — omit onChange
|
|
2669
|
+
<RatingStars value={4.2} accessibilityLabel="Average rating: 4.2 out of 5" />
|
|
2670
|
+
|
|
2671
|
+
// Interactive — pass onChange
|
|
2672
|
+
<RatingStars value={rating} onChange={setRating} />
|
|
2673
|
+
```
|
|
2674
|
+
|
|
2675
|
+
Star rating display and input — mirrors `@gnome-ui/react`'s `RatingStars`.
|
|
2676
|
+
Renders `role="radiogroup"` of `role="radio"` stars when `onChange` is
|
|
2677
|
+
provided, or a static `role="img"` when it isn't (e.g. showing an
|
|
2678
|
+
average/read-only rating); passing `disabled` always falls back to the
|
|
2679
|
+
read-only display even with `onChange` provided. The web version's
|
|
2680
|
+
`radiogroup` layer also owns an `onKeyDown` handler for arrow-key
|
|
2681
|
+
roving-tabindex navigation and a mouse-hover preview that doesn't commit
|
|
2682
|
+
until clicked — neither has a touch counterpart, so both drop, the same
|
|
2683
|
+
standing convention `ToggleGroup` already established for this package.
|
|
2684
|
+
What's left, tapping a star to commit that rating, is a strict subset of
|
|
2685
|
+
the web interaction rather than an approximation of it. Each star's fill
|
|
2686
|
+
uses `tintColor={theme.warningBgColor}` rather than `Icon`'s fixed
|
|
2687
|
+
`color="yellow"` — the source CSS reads `var(--gnome-warning-bg-color)`
|
|
2688
|
+
directly with no `color-mix()` darkening step, so the semantic warning
|
|
2689
|
+
token (already tracking dark mode and every contrast level) is the exact
|
|
2690
|
+
match, not an approximation through the fixed palette.
|
|
2691
|
+
|
|
2592
2692
|
### StatusBadge
|
|
2593
2693
|
|
|
2594
2694
|
```tsx
|
|
@@ -2905,6 +3005,216 @@ purely so the control can brighten on hover, and touch has no hover.
|
|
|
2905
3005
|
as `BottomTabBar`'s `bottomInset` — this package takes no dependency on
|
|
2906
3006
|
`react-native-safe-area-context` itself.
|
|
2907
3007
|
|
|
3008
|
+
### Calendar
|
|
3009
|
+
|
|
3010
|
+
```tsx
|
|
3011
|
+
import { Calendar } from '@gnome-ui/react-native';
|
|
3012
|
+
|
|
3013
|
+
const [value, setValue] = useState<Date | null>(null);
|
|
3014
|
+
|
|
3015
|
+
<Calendar value={value} onChange={setValue} />
|
|
3016
|
+
|
|
3017
|
+
<Calendar
|
|
3018
|
+
min={new Date(2024, 0, 1)}
|
|
3019
|
+
max={new Date(2024, 11, 31)}
|
|
3020
|
+
showWeekNumbers
|
|
3021
|
+
weekStartsOn={0}
|
|
3022
|
+
/>
|
|
3023
|
+
```
|
|
3024
|
+
|
|
3025
|
+
Month-grid date display — mirrors `GtkCalendar` and `@gnome-ui/react`'s own
|
|
3026
|
+
`Calendar`, ported from its `Calendar`+`CalendarBase`+`calendarUtils.ts`
|
|
3027
|
+
split. `calendarUtils.ts`'s pure date-math helpers port verbatim (zero DOM
|
|
3028
|
+
dependency on the web side already), the same `fileType.ts`/
|
|
3029
|
+
`coachMarkUtils.ts` precedent for dependency-free logic not worth a shared
|
|
3030
|
+
package.
|
|
3031
|
+
|
|
3032
|
+
**The web version's entire keyboard layer drops here** — roving tabindex,
|
|
3033
|
+
arrow keys, PageUp/Down, Home/End, Enter/Space are all gone, replaced by
|
|
3034
|
+
plain tap-to-select on every day/month/year cell. Unlike `Slider`'s 1D
|
|
3035
|
+
`accessibilityRole="adjustable"` fallback, a full 2D date grid has no
|
|
3036
|
+
screen-reader analog to page through, so tap-to-select is the strict touch
|
|
3037
|
+
subset of the web interaction — the same trade-off `RatingStars`/
|
|
3038
|
+
`ToggleGroup`/`ColorPicker` already made. The heading label still cycles
|
|
3039
|
+
day grid → month grid → year grid on tap, exactly as on the web, so a
|
|
3040
|
+
distant year is two taps away rather than many pages of month navigation.
|
|
3041
|
+
|
|
3042
|
+
`role="grid"`/`"row"` port 1:1 from RN's web-aligned `Role` union;
|
|
3043
|
+
`"gridcell"` isn't in that union, so day/month/year cells fall back to
|
|
3044
|
+
`"cell"` — the same kind of substitution `BoxedList` (`"list"`) and
|
|
3045
|
+
`ComboRow` (`"listbox"` → `"list"`) already made. The grid and its rows
|
|
3046
|
+
deliberately skip `accessible`, the `ToggleGroup`-corrected pattern: with
|
|
3047
|
+
many independently-focusable day cells inside, setting it would collapse
|
|
3048
|
+
the whole month into one VoiceOver stop. Day-name and week-number headers
|
|
3049
|
+
hold no interactive children, so they do set `accessible` to be announced
|
|
3050
|
+
as a single unit.
|
|
3051
|
+
|
|
3052
|
+
`visibleMonths` (side-by-side month panels) and `autoFocus`
|
|
3053
|
+
(keyboard-focus-on-mount) are dropped outright, not merely unimplemented —
|
|
3054
|
+
both are desktop/keyboard concerns with no honest phone-width or
|
|
3055
|
+
no-keyboard counterpart. `locale` is dropped too, in favor of the app-wide
|
|
3056
|
+
locale `GnomeProvider` already exposes through `useDateTimeFormatter` —
|
|
3057
|
+
`Calendar` is the first component in this package to seriously exercise
|
|
3058
|
+
that hook, and every `Intl.DateTimeFormatOptions` object it passes is
|
|
3059
|
+
hoisted to module scope so the hook's own `useMemo`-over-`options` identity
|
|
3060
|
+
check actually holds across renders. Day and drill-down cells are equal-
|
|
3061
|
+
width flex rows (7 columns for days, matching `getCalendarWeeks`'s fixed
|
|
3062
|
+
6-row output; 4 columns for the 12-cell month/year grids) rather than a
|
|
3063
|
+
CSS Grid port, since Yoga has no grid layout at all.
|
|
3064
|
+
|
|
3065
|
+
No `CalendarBase` split was extracted ahead of need — it was pulled out of
|
|
3066
|
+
this component's grid/navigation logic once `CalendarRange` actually
|
|
3067
|
+
shipped and needed it (see below), not carried as unused abstraction before
|
|
3068
|
+
that. The extraction changed no observable behavior: `Calendar`'s full
|
|
3069
|
+
pre-existing test suite passed unmodified against the refactor.
|
|
3070
|
+
|
|
3071
|
+
### CalendarRange
|
|
3072
|
+
|
|
3073
|
+
```tsx
|
|
3074
|
+
import { CalendarRange, type DateRange } from '@gnome-ui/react-native';
|
|
3075
|
+
|
|
3076
|
+
const [range, setRange] = useState<DateRange | null>(null);
|
|
3077
|
+
|
|
3078
|
+
<CalendarRange value={range} onChange={setRange} />
|
|
3079
|
+
|
|
3080
|
+
<CalendarRange minRange={2} maxRange={5} value={range} onChange={setRange} />
|
|
3081
|
+
```
|
|
3082
|
+
|
|
3083
|
+
Start/end date-range selection on the same grid engine as `Calendar` —
|
|
3084
|
+
month/year drill-down, `min`/`max`, week numbers — via `CalendarBase`, the
|
|
3085
|
+
shared engine `Calendar` extracted once this component actually needed it
|
|
3086
|
+
rather than adding the split ahead of need.
|
|
3087
|
+
|
|
3088
|
+
The first tap anchors the range; the second commits it, so `onChange` only
|
|
3089
|
+
ever fires with **both** ends filled in. Tapping backwards is fine — the
|
|
3090
|
+
pair is ordered before it's emitted.
|
|
3091
|
+
|
|
3092
|
+
**No live drag-preview band, unlike the web version.** The web
|
|
3093
|
+
`CalendarRange` grows the band under the mouse/keyboard focus between the
|
|
3094
|
+
two clicks; RN has no hover, and this package's whole keyboard layer is
|
|
3095
|
+
already dropped (see `Calendar`'s own doc comment above), so there's no
|
|
3096
|
+
signal to preview against. Rather than reach for a `PanResponder` drag
|
|
3097
|
+
gesture — a materially bigger feature the web version doesn't even have,
|
|
3098
|
+
since it's mouse-hover, not drag — the anchor day just shows as a normal
|
|
3099
|
+
selected day until the second tap lands and the full band appears at once,
|
|
3100
|
+
the same strict-touch-subset trade-off `RatingStars`/`ToggleGroup`/
|
|
3101
|
+
`Calendar` itself already made for other dropped hover/keyboard affordances.
|
|
3102
|
+
|
|
3103
|
+
The range band has no CSS `::before`/pseudo-element or `color-mix()` to
|
|
3104
|
+
lean on in RN — it's an absolutely-positioned `View` behind each day's
|
|
3105
|
+
button, bleeding half the cell's own padding into its neighbor via a
|
|
3106
|
+
negative inset (the JS-math equivalent of the web CSS's
|
|
3107
|
+
`inset-inline: calc(gap/-2)` bleed) so consecutive in-range days read as one
|
|
3108
|
+
continuous stripe under the round day buttons; end caps skip the bleed on
|
|
3109
|
+
their outer side and round that corner instead, so a single-day range
|
|
3110
|
+
composes into a full pill from the same two conditionals rather than a
|
|
3111
|
+
separate case. Tint alpha uses the `#RRGGBBAA` hex-suffix substitution
|
|
3112
|
+
`Blockquote`/`Chip` already established for the web's `color-mix()`.
|
|
3113
|
+
|
|
3114
|
+
### DatePicker
|
|
3115
|
+
|
|
3116
|
+
```tsx
|
|
3117
|
+
import { DatePicker } from '@gnome-ui/react-native';
|
|
3118
|
+
|
|
3119
|
+
const [value, setValue] = useState<Date | null>(null);
|
|
3120
|
+
|
|
3121
|
+
<DatePicker label="Date" value={value} onChange={setValue} />
|
|
3122
|
+
|
|
3123
|
+
<DatePicker
|
|
3124
|
+
label="Meeting time"
|
|
3125
|
+
showTime
|
|
3126
|
+
hourCycle={12}
|
|
3127
|
+
value={value}
|
|
3128
|
+
onChange={setValue}
|
|
3129
|
+
/>
|
|
3130
|
+
```
|
|
3131
|
+
|
|
3132
|
+
A `Popover`-anchored `Calendar` behind a text-entry-styled trigger, mirroring
|
|
3133
|
+
the `GtkCalendar` + `GtkPopover` composition GNOME apps use for date entry.
|
|
3134
|
+
Composed entirely from this package's own already-shipped pieces —
|
|
3135
|
+
`Popover` (Tier 5) and `Calendar` (Tier 20) — rather than reinventing any
|
|
3136
|
+
position-computation code, per this package's standing pitfall about not
|
|
3137
|
+
re-deriving `Popover`/`Dropdown`'s already-shipped trigger-rect + panel-size
|
|
3138
|
+
positioning.
|
|
3139
|
+
|
|
3140
|
+
The trigger is a plain themed `Pressable` styled like `Dropdown`'s own
|
|
3141
|
+
text-entry-look trigger (bordered row, dimmed placeholder, trailing
|
|
3142
|
+
`XOfficeCalendar` icon) rather than a literal `TextField` composition — a
|
|
3143
|
+
`TextField` wraps a real editable `TextInput`, which a date-picker trigger
|
|
3144
|
+
never wants (it opens a panel, it doesn't accept typed text).
|
|
3145
|
+
`locale`/`formatOptions` are dropped in favor of `GnomeProvider`'s app-wide
|
|
3146
|
+
`useDateTimeFormatter`, the same convention `Calendar` itself just
|
|
3147
|
+
established: reading the hook's own `{...dateTimeFormat, ...options}` merge
|
|
3148
|
+
confirmed it already expresses `dateStyle`+`timeStyle`+`hourCycle` together,
|
|
3149
|
+
so three fixed, module-scope option objects (date-only, 24-hour, 12-hour)
|
|
3150
|
+
cover every `showTime`/`hourCycle` combination without needing an arbitrary
|
|
3151
|
+
caller-supplied `formatOptions` escape hatch. The web version's entire
|
|
3152
|
+
keyboard layer drops (no `ArrowDown`-opens-the-popover, no `Enter`/`Space`
|
|
3153
|
+
on the trigger), and `Calendar`'s own already-dropped `autoFocus` prop is
|
|
3154
|
+
correctly never wired here either.
|
|
3155
|
+
|
|
3156
|
+
Picking a day keeps the popover open when `showTime` is on — only the Done
|
|
3157
|
+
button closes it, so the time columns stay reachable — and closes it
|
|
3158
|
+
immediately otherwise, ported as plain state logic from the web version's
|
|
3159
|
+
`handleSelect`, no web-only API involved.
|
|
3160
|
+
|
|
3161
|
+
**`showTime` brought its own dependency.** The web `TimePicker`'s
|
|
3162
|
+
`TimeFields`/`timeUtils.ts` (hour/minute/AM-PM `SpinButton` columns plus
|
|
3163
|
+
pure 12/24-hour math) first ported into `DatePicker`'s own folder as an
|
|
3164
|
+
internal, non-exported module, the same "shared piece built by its first
|
|
3165
|
+
real consumer" precedent `IconButton` set for `Drawer`'s `rail` — since
|
|
3166
|
+
relocated to `TimePicker`'s own folder now that it's shipped (see below),
|
|
3167
|
+
with `DatePicker` importing it from there instead of carrying its own copy.
|
|
3168
|
+
`timeUtils.ts` ports verbatim (zero DOM dependency already), and
|
|
3169
|
+
`TimeFields` rebuilds on this
|
|
3170
|
+
package's own `SpinButton` (Tier 5) — its `wrap` boolean and
|
|
3171
|
+
`format: (n: number) => string` callback already cover the AM/PM column's
|
|
3172
|
+
"numeric spinner whose `format` maps 0/1 to text" trick with no new prop
|
|
3173
|
+
needed, confirmed by reading `SpinButton.tsx` before wiring it up.
|
|
3174
|
+
|
|
3175
|
+
`SpinButton` has a fixed per-column minimum width (two 36 dp buttons plus a
|
|
3176
|
+
56 dp value `Text`, ~130 dp), so a 12-hour row of three columns
|
|
3177
|
+
(hours/minutes/AM-PM) can approach or exceed a narrow phone's screen width
|
|
3178
|
+
even after widening the popover panel past its default 320 dp cap —
|
|
3179
|
+
`Popover`'s own `panelStyle` prop (a `StyleProp<ViewStyle>`, not a
|
|
3180
|
+
`panelClassName` string the way the web version's CSS-module trigger works;
|
|
3181
|
+
confirmed by reading `Popover.tsx` first) raises the cap to 420 dp for
|
|
3182
|
+
`showTime`, and the footer row itself is `flexWrap: 'wrap'` so the Done
|
|
3183
|
+
button drops to its own line instead of clipping or forcing horizontal
|
|
3184
|
+
scroll.
|
|
3185
|
+
|
|
3186
|
+
### TimePicker
|
|
3187
|
+
|
|
3188
|
+
```tsx
|
|
3189
|
+
import { TimePicker } from '@gnome-ui/react-native';
|
|
3190
|
+
|
|
3191
|
+
const [value, setValue] = useState<{ hours: number; minutes: number } | null>(null);
|
|
3192
|
+
|
|
3193
|
+
<TimePicker label="Time" value={value} onChange={setValue} />
|
|
3194
|
+
|
|
3195
|
+
<TimePicker label="Reminder" hourCycle={12} value={value} onChange={setValue} />
|
|
3196
|
+
```
|
|
3197
|
+
|
|
3198
|
+
Paired hour/minute `SpinButton` columns behind the same bordered/dimmed-
|
|
3199
|
+
placeholder/trailing-icon trigger style `DatePicker` established — mirrors
|
|
3200
|
+
the `GtkSpinButton` + `GtkPopover` composition GNOME apps use for time
|
|
3201
|
+
entry, with 12- and 24-hour support.
|
|
3202
|
+
|
|
3203
|
+
`TimeFields`/`timeUtils.ts` — the hour/minute/AM-PM `SpinButton` columns and
|
|
3204
|
+
their pure 12/24-hour math — live here now, relocated out of `DatePicker`'s
|
|
3205
|
+
folder once this component gave them a real public home; `DatePicker`'s
|
|
3206
|
+
`showTime` footer imports them from here instead of carrying its own copy.
|
|
3207
|
+
|
|
3208
|
+
Unlike `DatePicker`'s `showTime` footer, there's no Done button: with no
|
|
3209
|
+
calendar-day tap to disambiguate from a close, each `SpinButton` column
|
|
3210
|
+
commits live via `onChange`, and the popover only closes by tapping outside
|
|
3211
|
+
or the trigger again — the same convention `Dropdown` already established.
|
|
3212
|
+
Formatting uses `hour`/`minute` `Intl.DateTimeFormat` component options
|
|
3213
|
+
(`{hour: '2-digit', minute: '2-digit', hourCycle}`) rather than `DatePicker`'s
|
|
3214
|
+
`dateStyle`+`timeStyle` pairing, since there's no date to render — ported
|
|
3215
|
+
straight from the web version's own formatter call, still routed through
|
|
3216
|
+
`GnomeProvider`'s `useDateTimeFormatter` for locale consistency.
|
|
3217
|
+
|
|
2908
3218
|
## Installation
|
|
2909
3219
|
|
|
2910
3220
|
```bash
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { CalendarView } from './CalendarBase';
|
|
3
|
+
import { WeekStart } from './calendarUtils';
|
|
4
|
+
export type { CalendarView } from './CalendarBase';
|
|
5
|
+
export interface CalendarProps {
|
|
6
|
+
/** Controlled selected date. Pass `null` for "no selection". */
|
|
7
|
+
value?: Date | null;
|
|
8
|
+
/** Initial selected date when uncontrolled. Defaults to `null`. */
|
|
9
|
+
defaultValue?: Date | null;
|
|
10
|
+
/** Called when the user taps a day. */
|
|
11
|
+
onChange?: (date: Date) => void;
|
|
12
|
+
/** Controlled displayed month (any date within the desired month). */
|
|
13
|
+
month?: Date;
|
|
14
|
+
/**
|
|
15
|
+
* Initial displayed month when uncontrolled. Defaults to the selected
|
|
16
|
+
* date's month, falling back to the current month.
|
|
17
|
+
*/
|
|
18
|
+
defaultMonth?: Date;
|
|
19
|
+
/** Called when the displayed month changes via navigation. */
|
|
20
|
+
onMonthChange?: (month: Date) => void;
|
|
21
|
+
/** Earliest selectable date (inclusive). Earlier days are disabled. */
|
|
22
|
+
min?: Date;
|
|
23
|
+
/** Latest selectable date (inclusive). Later days are disabled. */
|
|
24
|
+
max?: Date;
|
|
25
|
+
/**
|
|
26
|
+
* First day of the week: `0` (Sunday) … `6` (Saturday). Defaults to `1`
|
|
27
|
+
* (Monday) — the GNOME default across most locales.
|
|
28
|
+
*/
|
|
29
|
+
weekStartsOn?: WeekStart;
|
|
30
|
+
/** Show the month/year heading with prev/next navigation. Mirrors `GtkCalendar:show-heading`. Defaults to `true`. */
|
|
31
|
+
showHeading?: boolean;
|
|
32
|
+
/** Show the abbreviated day-name column headers. Mirrors `GtkCalendar:show-day-names`. Defaults to `true`. */
|
|
33
|
+
showDayNames?: boolean;
|
|
34
|
+
/** Show an ISO week-number column. Mirrors `GtkCalendar:show-week-numbers`. Defaults to `false`. */
|
|
35
|
+
showWeekNumbers?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Turn the heading label into a button that drills down day grid → month
|
|
38
|
+
* grid → year grid, the way modern date pickers let you jump years
|
|
39
|
+
* without paging month by month. Requires `showHeading`. Defaults to `true`.
|
|
40
|
+
*/
|
|
41
|
+
showViewSwitcher?: boolean;
|
|
42
|
+
/** Grid shown on mount: `'days'`, `'months'` or `'years'`. Defaults to `'days'`. */
|
|
43
|
+
defaultView?: CalendarView;
|
|
44
|
+
/** Called when the drill-down view changes. */
|
|
45
|
+
onViewChange?: (view: CalendarView) => void;
|
|
46
|
+
/** Accessible label for the grid. Defaults to the current heading label. */
|
|
47
|
+
accessibilityLabel?: string;
|
|
48
|
+
style?: StyleProp<ViewStyle>;
|
|
49
|
+
testID?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Month-grid date display — mirrors `GtkCalendar` and `@gnome-ui/react`'s own
|
|
53
|
+
* `Calendar`. Usable standalone (settings, forms) or as the panel inside
|
|
54
|
+
* `DatePicker`.
|
|
55
|
+
*
|
|
56
|
+
* The heading label drills down day grid → month grid → year grid so a
|
|
57
|
+
* distant year is two taps away instead of many pages, exactly as on the web.
|
|
58
|
+
* Leading/trailing days from adjacent months are shown dimmed and remain
|
|
59
|
+
* tappable — tapping one navigates to that month, matching `GtkCalendar`.
|
|
60
|
+
*
|
|
61
|
+
* A thin single-day `CalendarSelectionModel` over `CalendarBase` — the
|
|
62
|
+
* shared grid engine (month window, heading, day/month/year drill-down)
|
|
63
|
+
* extracted once `CalendarRange` actually needed it, per the note this
|
|
64
|
+
* component left when it first shipped rather than adding the split ahead
|
|
65
|
+
* of need.
|
|
66
|
+
*
|
|
67
|
+
* **The web version's entire keyboard layer drops here** (roving tabindex,
|
|
68
|
+
* arrow keys, PageUp/Down, Home/End, Enter/Space) — this package is
|
|
69
|
+
* touch-first, and unlike `Slider`'s 1D `accessibilityRole="adjustable"`
|
|
70
|
+
* escape hatch, there is no screen-reader analog for paging a full 2D date
|
|
71
|
+
* grid. Tap-to-select on each cell is the strict touch subset of the web
|
|
72
|
+
* interaction, the same trade-off already made for `RatingStars`/
|
|
73
|
+
* `ToggleGroup`/`ColorPicker`. `role="grid"`/`"row"` port 1:1 from RN's
|
|
74
|
+
* web-aligned `Role` union; `"gridcell"` doesn't exist in that union, so
|
|
75
|
+
* cells fall back to `"cell"`, the same kind of substitution `BoxedList`
|
|
76
|
+
* (`"list"`) and `ComboRow` (`"listbox"` → `"list"`) already made. The grid
|
|
77
|
+
* and its rows deliberately skip `accessible` — the `ToggleGroup`-corrected
|
|
78
|
+
* pattern: setting it on a container with many independently-focusable
|
|
79
|
+
* children (each day) would collapse the whole month into one VoiceOver
|
|
80
|
+
* stop. Column/row *headers* (day names, week numbers) hold no interactive
|
|
81
|
+
* children, so they do set `accessible` to be announced as a single unit.
|
|
82
|
+
*
|
|
83
|
+
* `visibleMonths` (side-by-side month panels) and `autoFocus`
|
|
84
|
+
* (keyboard-focus-on-mount) are dropped, not merely unimplemented: both are
|
|
85
|
+
* desktop/keyboard concerns with no honest RN counterpart — a phone screen
|
|
86
|
+
* has no room for two month panels side by side, and there is no keyboard to
|
|
87
|
+
* focus. `locale` is dropped too, in favor of the app-wide locale
|
|
88
|
+
* `GnomeProvider` already exposes through `useDateTimeFormatter`.
|
|
89
|
+
*
|
|
90
|
+
* For start/end selection use `CalendarRange`, which shares this same grid
|
|
91
|
+
* engine via `CalendarBase`.
|
|
92
|
+
*
|
|
93
|
+
* @see https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Calendar.html
|
|
94
|
+
*/
|
|
95
|
+
export declare const Calendar: ({ value: controlledValue, defaultValue, onChange, min, max, ...props }: CalendarProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { WeekStart } from './calendarUtils';
|
|
4
|
+
/** Which grid the calendar is currently showing. */
|
|
5
|
+
export type CalendarView = 'days' | 'months' | 'years';
|
|
6
|
+
/** How one day cell should be painted, as decided by the selection model. */
|
|
7
|
+
export interface CalendarDayState {
|
|
8
|
+
/** Endpoint of the selection — filled accent pill. */
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
/** Left edge of a range. */
|
|
11
|
+
rangeStart?: boolean;
|
|
12
|
+
/** Right edge of a range. */
|
|
13
|
+
rangeEnd?: boolean;
|
|
14
|
+
/** Strictly between the two ends of a range. */
|
|
15
|
+
inRange?: boolean;
|
|
16
|
+
/** The range being painted is a preview, not a committed selection. */
|
|
17
|
+
preview?: boolean;
|
|
18
|
+
/** Appended to the day's accessible label, e.g. `', start of range'`. */
|
|
19
|
+
labelSuffix?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Everything `CalendarBase` needs to know about *what is selected*, so the
|
|
23
|
+
* grid engine (month/year navigation, day/month/year drill-down) stays free
|
|
24
|
+
* of selection semantics and can drive both `Calendar` and `CalendarRange`.
|
|
25
|
+
*
|
|
26
|
+
* Trimmed from `@gnome-ui/react`'s own `CalendarSelectionModel`: no
|
|
27
|
+
* `seedFocus`/`hoverDay`/`focusDayChange`/`escape` — those exist only to
|
|
28
|
+
* drive the web version's keyboard roving-tabindex and mouse-hover range
|
|
29
|
+
* preview, and this package drops the entire keyboard layer the same way
|
|
30
|
+
* `Calendar` itself already does (no honest RN touch equivalent). Without a
|
|
31
|
+
* hover signal, a range picker's "preview" is just its own anchor day until
|
|
32
|
+
* the second tap commits — `CalendarRange` below relies on that rather than
|
|
33
|
+
* tracking a separate preview day.
|
|
34
|
+
*/
|
|
35
|
+
export interface CalendarSelectionModel {
|
|
36
|
+
dayState: (date: Date) => CalendarDayState;
|
|
37
|
+
isDayDisabled?: (date: Date) => boolean;
|
|
38
|
+
activateDay: (date: Date) => void;
|
|
39
|
+
isMonthSelected?: (month: Date) => boolean;
|
|
40
|
+
isYearSelected?: (year: number) => boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface CalendarBaseProps {
|
|
43
|
+
selection: CalendarSelectionModel;
|
|
44
|
+
/** Controlled displayed month (any date within the desired month). */
|
|
45
|
+
month?: Date;
|
|
46
|
+
/**
|
|
47
|
+
* Initial displayed month when uncontrolled. Defaults to `fallbackMonth`,
|
|
48
|
+
* falling back to the current month.
|
|
49
|
+
*/
|
|
50
|
+
defaultMonth?: Date;
|
|
51
|
+
/** Called when the displayed month changes via navigation. */
|
|
52
|
+
onMonthChange?: (month: Date) => void;
|
|
53
|
+
/** Seeds the displayed month when neither `month` nor `defaultMonth` is given. */
|
|
54
|
+
fallbackMonth?: Date | null;
|
|
55
|
+
/** Earliest selectable date (inclusive). Also disables out-of-range months/years. */
|
|
56
|
+
min?: Date;
|
|
57
|
+
/** Latest selectable date (inclusive). Also disables out-of-range months/years. */
|
|
58
|
+
max?: Date;
|
|
59
|
+
weekStartsOn?: WeekStart;
|
|
60
|
+
showHeading?: boolean;
|
|
61
|
+
showDayNames?: boolean;
|
|
62
|
+
showWeekNumbers?: boolean;
|
|
63
|
+
showViewSwitcher?: boolean;
|
|
64
|
+
defaultView?: CalendarView;
|
|
65
|
+
onViewChange?: (view: CalendarView) => void;
|
|
66
|
+
accessibilityLabel?: string;
|
|
67
|
+
/** Rendered inside the root, after the grid — status live regions, presets. */
|
|
68
|
+
footer?: ReactNode;
|
|
69
|
+
style?: StyleProp<ViewStyle>;
|
|
70
|
+
testID?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The shared calendar grid engine: month window, heading navigation, and the
|
|
74
|
+
* day / month / year drill-down grids. It owns *navigation*; `selection`
|
|
75
|
+
* owns *what is selected* — extracted from `Calendar` once `CalendarRange`
|
|
76
|
+
* actually needed the same engine, per the standing note `Calendar` left
|
|
77
|
+
* when it shipped rather than adding this split ahead of need.
|
|
78
|
+
*
|
|
79
|
+
* Not exported from the package barrel — `Calendar` and `CalendarRange` are
|
|
80
|
+
* its public faces, the same "internal engine, public wrapper" shape
|
|
81
|
+
* `TimeFields`/`TimePicker` already established.
|
|
82
|
+
*/
|
|
83
|
+
export declare const CalendarBase: ({ selection, month: controlledMonth, defaultMonth, onMonthChange, fallbackMonth, min, max, weekStartsOn, showHeading, showDayNames, showWeekNumbers, showViewSwitcher, defaultView, onViewChange, accessibilityLabel, footer, style, testID, }: CalendarBaseProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure date helpers for `Calendar`. Duplicated verbatim from
|
|
3
|
+
* `@gnome-ui/react`'s `calendarUtils.ts` rather than imported cross-package
|
|
4
|
+
* — the same `fileType.ts`/`coachMarkUtils.ts` precedent for dependency-free
|
|
5
|
+
* logic that isn't worth a shared package for one file's worth of code. Zero
|
|
6
|
+
* DOM dependency on the web side already, so nothing here changes for RN.
|
|
7
|
+
*
|
|
8
|
+
* Every helper operates on the local time zone — a "day" is a wall-clock
|
|
9
|
+
* day, never a UTC instant.
|
|
10
|
+
*
|
|
11
|
+
* `fromISODateKey` doesn't make the cut: the web version uses it to decode
|
|
12
|
+
* `data-date` attributes from a single delegated `onMouseOver` listener (a
|
|
13
|
+
* hover-preview feature with no touch equivalent), and nothing else in this
|
|
14
|
+
* package needs to parse an ISO key back into a `Date`. `toISODateKey` stays
|
|
15
|
+
* — every day/week still wants a stable string for React `key`s and test
|
|
16
|
+
* IDs.
|
|
17
|
+
*/
|
|
18
|
+
export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
|
19
|
+
/** Midnight (local) of the given date, as a fresh `Date`. */
|
|
20
|
+
export declare const startOfDay: (date: Date) => Date;
|
|
21
|
+
/** First day of the month containing `date`, at local midnight. */
|
|
22
|
+
export declare const startOfMonth: (date: Date) => Date;
|
|
23
|
+
/** `true` when both dates fall on the same calendar day. */
|
|
24
|
+
export declare const isSameDay: (a: Date, b: Date) => boolean;
|
|
25
|
+
/** `true` when both dates fall in the same calendar month. */
|
|
26
|
+
export declare const isSameMonth: (a: Date, b: Date) => boolean;
|
|
27
|
+
/** `date` shifted by `amount` days (may be negative). */
|
|
28
|
+
export declare const addDays: (date: Date, amount: number) => Date;
|
|
29
|
+
/**
|
|
30
|
+
* The date `year-month-day`, with the day clamped to the last valid day of that
|
|
31
|
+
* month. Used whenever a month or year jump must keep the day-of-month intact.
|
|
32
|
+
*/
|
|
33
|
+
export declare const clampDayToMonth: (year: number, month: number, day: number) => Date;
|
|
34
|
+
/**
|
|
35
|
+
* `date` shifted by `amount` months, keeping the day-of-month where possible
|
|
36
|
+
* and clamping to the last valid day otherwise (e.g. Jan 31 + 1 month → Feb 28).
|
|
37
|
+
*/
|
|
38
|
+
export declare const addMonths: (date: Date, amount: number) => Date;
|
|
39
|
+
/**
|
|
40
|
+
* `date` shifted by `amount` years, clamping the day-of-month where the target
|
|
41
|
+
* year is shorter (e.g. 29 Feb 2028 - 1 year → 28 Feb 2027).
|
|
42
|
+
*/
|
|
43
|
+
export declare const addYears: (date: Date, amount: number) => Date;
|
|
44
|
+
/** How many years one page of the year view shows. */
|
|
45
|
+
export declare const YEARS_PER_PAGE = 12;
|
|
46
|
+
/** First year of the fixed-size page of years containing `year`. */
|
|
47
|
+
export declare const startOfYearPage: (year: number) => number;
|
|
48
|
+
/** Distance in days from the week's start to `date`, given the first weekday. */
|
|
49
|
+
export declare const weekdayOffset: (date: Date, weekStartsOn: WeekStart) => number;
|
|
50
|
+
/**
|
|
51
|
+
* The six-week grid (always 6 rows × 7 days) that the web `Calendar` renders
|
|
52
|
+
* for a month — leading/trailing days belong to the adjacent months. A fixed
|
|
53
|
+
* row count avoids the layout jump months of differing length would
|
|
54
|
+
* otherwise cause.
|
|
55
|
+
*/
|
|
56
|
+
export declare const getCalendarWeeks: (month: Date, weekStartsOn: WeekStart) => Date[][];
|
|
57
|
+
/** ISO-8601 week number (weeks start Monday; week 1 contains the first Thursday). */
|
|
58
|
+
export declare const isoWeekNumber: (date: Date) => number;
|
|
59
|
+
/** `true` when `date` is outside the inclusive `[min, max]` range. */
|
|
60
|
+
export declare const isOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
|
|
61
|
+
/** `true` when every day of `date`'s month falls outside the inclusive `[min, max]` range. */
|
|
62
|
+
export declare const isMonthOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
|
|
63
|
+
/** `true` when every day of `date`'s year falls outside the inclusive `[min, max]` range. */
|
|
64
|
+
export declare const isYearOutOfRange: (date: Date, min?: Date, max?: Date) => boolean;
|
|
65
|
+
/** A stable `YYYY-MM-DD` key for a date, used for React `key`s and test IDs. */
|
|
66
|
+
export declare const toISODateKey: (date: Date) => string;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { CalendarView } from '../Calendar/CalendarBase';
|
|
3
|
+
import { WeekStart } from '../Calendar/calendarUtils';
|
|
4
|
+
import { DateRange, SelectedDateRange } from './rangeUtils';
|
|
5
|
+
export type { DateRange, SelectedDateRange } from './rangeUtils';
|
|
6
|
+
export interface CalendarRangeProps {
|
|
7
|
+
/** Controlled range. Either end may be `null` while nothing is chosen. */
|
|
8
|
+
value?: DateRange | null;
|
|
9
|
+
/** Initial range when uncontrolled. Defaults to an empty range. */
|
|
10
|
+
defaultValue?: DateRange | null;
|
|
11
|
+
/**
|
|
12
|
+
* Called with the finished range — **only once both ends have a value**.
|
|
13
|
+
* The first tap merely anchors the range, so this never fires with a half
|
|
14
|
+
* selection.
|
|
15
|
+
*/
|
|
16
|
+
onChange?: (range: SelectedDateRange) => void;
|
|
17
|
+
/** Called with the anchor day when the first of the two taps lands. */
|
|
18
|
+
onRangeStart?: (date: Date) => void;
|
|
19
|
+
/** Controlled displayed month (any date within the desired month). */
|
|
20
|
+
month?: Date;
|
|
21
|
+
/**
|
|
22
|
+
* Initial displayed month when uncontrolled. Defaults to the range's
|
|
23
|
+
* start month, falling back to the current month.
|
|
24
|
+
*/
|
|
25
|
+
defaultMonth?: Date;
|
|
26
|
+
/** Called when the displayed month changes via navigation. */
|
|
27
|
+
onMonthChange?: (month: Date) => void;
|
|
28
|
+
/** Earliest selectable date (inclusive). */
|
|
29
|
+
min?: Date;
|
|
30
|
+
/** Latest selectable date (inclusive). */
|
|
31
|
+
max?: Date;
|
|
32
|
+
/** Shortest range the user may commit, in days (both ends counted). Defaults to `1`. */
|
|
33
|
+
minRange?: number;
|
|
34
|
+
/** Longest range the user may commit, in days (both ends counted). Unlimited by default. */
|
|
35
|
+
maxRange?: number;
|
|
36
|
+
weekStartsOn?: WeekStart;
|
|
37
|
+
showHeading?: boolean;
|
|
38
|
+
showDayNames?: boolean;
|
|
39
|
+
showWeekNumbers?: boolean;
|
|
40
|
+
showViewSwitcher?: boolean;
|
|
41
|
+
defaultView?: CalendarView;
|
|
42
|
+
onViewChange?: (view: CalendarView) => void;
|
|
43
|
+
accessibilityLabel?: string;
|
|
44
|
+
style?: StyleProp<ViewStyle>;
|
|
45
|
+
testID?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Start/end date-range selection driving the same grid engine as `Calendar`
|
|
49
|
+
* — month/year drill-down, `min`/`max`, week numbers — via `CalendarBase`,
|
|
50
|
+
* the shared engine `Calendar` extracted once this component actually
|
|
51
|
+
* needed it.
|
|
52
|
+
*
|
|
53
|
+
* The first tap anchors the range; the second commits it. `onChange`
|
|
54
|
+
* therefore only ever fires with **both** ends filled in. Tapping backwards
|
|
55
|
+
* is fine — the pair is ordered before it is emitted.
|
|
56
|
+
*
|
|
57
|
+
* **No live drag-preview band, unlike the web version.** The web
|
|
58
|
+
* `CalendarRange` grows the band under the mouse/keyboard focus between the
|
|
59
|
+
* two clicks; RN has no hover and this package's whole keyboard layer is
|
|
60
|
+
* already dropped (see `Calendar`'s own doc comment), so there is no signal
|
|
61
|
+
* to preview against. The anchor day simply shows as a normal selected day
|
|
62
|
+
* until the second tap lands and the full band appears at once — the
|
|
63
|
+
* touch-first subset of the same interaction, the same kind of trade-off
|
|
64
|
+
* `RatingStars`/`ToggleGroup`/`Calendar` itself already made for dropped
|
|
65
|
+
* hover/keyboard affordances. `CalendarBase`'s `CalendarSelectionModel` was
|
|
66
|
+
* trimmed to match — no `hoverDay`/`focusDayChange` to wire up here.
|
|
67
|
+
*
|
|
68
|
+
* `visibleMonths` (side-by-side month panels, the usual desktop range-picker
|
|
69
|
+
* shape) has no phone-width equivalent — dropped, the same `Calendar`
|
|
70
|
+
* `visibleMonths` precedent. `locale` is dropped too, in favor of
|
|
71
|
+
* `GnomeProvider`'s app-wide `useDateTimeFormatter`.
|
|
72
|
+
*
|
|
73
|
+
* @see https://gnome.pages.gitlab.gnome.org/gtk/gtk4/class.Calendar.html
|
|
74
|
+
*/
|
|
75
|
+
export declare const CalendarRange: ({ value: controlledValue, defaultValue, onChange, onRangeStart, min, max, minRange, maxRange, ...props }: CalendarRangeProps) => import("react/jsx-runtime").JSX.Element;
|