@lotics/ui 47.6.1 → 47.7.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/docs/catalog.md +9 -3
- package/package.json +1 -1
- package/src/filter_band.tsx +18 -8
- package/src/switcher.tsx +27 -4
package/docs/catalog.md
CHANGED
|
@@ -1045,8 +1045,11 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1045
1045
|
- **`card_select_item`** — `CardSelectItem`: a bordered, card-shaped button; `selected` =
|
|
1046
1046
|
persistent ring (reads as `aria-pressed`) — the org-picker / entity-switcher item.
|
|
1047
1047
|
- **`switcher`** — `Switcher`: current-item trigger opening a popover of `MenuButton` items
|
|
1048
|
-
(`SwitcherItem { id, label }`, `currentId`, `onSelect`) — the compact
|
|
1049
|
-
switcher.
|
|
1048
|
+
(`SwitcherItem { id, label, imageUri? }`, `currentId`, `onSelect`) — the compact
|
|
1049
|
+
entity/workspace switcher. A square `Avatar` appears on the trigger and every row as soon as
|
|
1050
|
+
ONE item carries `imageUri`, the rest falling back to initials, so an unpictured entry still
|
|
1051
|
+
lines up. `minItemsToRender` defaults to 2 (a single choice is clutter); lower it to 1 where
|
|
1052
|
+
the switcher states a scope the reader has no other way to see.
|
|
1050
1053
|
- **`count`** — `Count`: a 20px count disc that grows into a pill past one digit (`color` highlight|muted|red).
|
|
1051
1054
|
- **`shortcut_badge`** — `ShortcutBadge`: the keycap hint pill — a zinc-50 badge rendering a
|
|
1052
1055
|
shortcut from a raw string or `ShortcutDescriptor` (⌘B on Mac, Ctrl+B elsewhere); null on
|
|
@@ -1772,7 +1775,10 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1772
1775
|
opened the screen for. Pass `activeCount` so the collapsed button still says the list is
|
|
1773
1776
|
filtered, and `onClearAll` to put one clear in the sheet. Compose the band with this rather
|
|
1774
1777
|
than a hand-rolled `<View flexDirection="row">`: a hand-rolled band is the one that does not
|
|
1775
|
-
collapse.
|
|
1778
|
+
collapse. **A facet with nothing to offer should render `null`, and the band then draws no
|
|
1779
|
+
trigger at all** — narrow as well as wide. Gate each chip on its own options (a status filter
|
|
1780
|
+
whose values are all already in view, a round picker before any round is recorded) rather than
|
|
1781
|
+
shipping an empty one; search and the action stay on the band either way.
|
|
1776
1782
|
- **`filter_chip`** — `FilterChip` + `selectSummary`: the toolbar filter pill hosting a
|
|
1777
1783
|
facet (options, a `Slider range`, a `Counter`). It draws NO bottom band of its own — the ×
|
|
1778
1784
|
on the pill is the clear, and the editor inside brings whatever actions it has, so a
|
package/package.json
CHANGED
package/src/filter_band.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useState, type ReactNode } from "react";
|
|
1
|
+
import { Children, createContext, useContext, useState, type ReactNode } from "react";
|
|
2
2
|
import { View } from "react-native";
|
|
3
3
|
import { Button } from "./button";
|
|
4
4
|
import { Dialog, DialogFooter, DialogHeader, DialogHeaderTitle, DialogScrollArea } from "./dialog";
|
|
@@ -58,6 +58,14 @@ export function FilterBand(props: FilterBandProps) {
|
|
|
58
58
|
const t = useLoticsLocale().filterBand;
|
|
59
59
|
const { small } = useScreenSize();
|
|
60
60
|
const [open, setOpen] = useState(false);
|
|
61
|
+
// WHETHER THERE IS ANYTHING TO COLLAPSE. `Children.toArray` drops the nulls a
|
|
62
|
+
// caller renders for a facet that has nothing to offer — a status filter whose
|
|
63
|
+
// options are all in view, a round picker before any round is recorded — so a
|
|
64
|
+
// band that draws no chips wide must not draw a button narrow. It did: the wide
|
|
65
|
+
// branch renders `children` and gets nothing, the narrow one rendered the
|
|
66
|
+
// trigger unconditionally, and the two presentations stopped agreeing at exactly
|
|
67
|
+
// the width where a control that opens an empty sheet costs the most.
|
|
68
|
+
const hasFilters = Children.toArray(children).length > 0;
|
|
61
69
|
|
|
62
70
|
if (!small) {
|
|
63
71
|
return (
|
|
@@ -77,14 +85,16 @@ export function FilterBand(props: FilterBandProps) {
|
|
|
77
85
|
return (
|
|
78
86
|
<View testID={testID} style={{ flexDirection: "row", alignItems: "center", gap: SPACE.sm }}>
|
|
79
87
|
{search != null && <View style={{ flexGrow: 1, flexShrink: 1, minWidth: 0 }}>{search}</View>}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
{hasFilters && (
|
|
89
|
+
<Button
|
|
90
|
+
color="secondary"
|
|
91
|
+
title={activeCount > 0 ? `${t.filters} (${activeCount})` : t.filters}
|
|
92
|
+
onPress={() => setOpen(true)}
|
|
93
|
+
testID={testID ? `${testID}-filters` : undefined}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
86
96
|
{action}
|
|
87
|
-
<Dialog open={open} onOpenChange={setOpen} width="92%" maxWidth={520}>
|
|
97
|
+
<Dialog open={open && hasFilters} onOpenChange={setOpen} width="92%" maxWidth={520}>
|
|
88
98
|
<DialogHeader>
|
|
89
99
|
<DialogHeaderTitle>{t.filters}</DialogHeaderTitle>
|
|
90
100
|
</DialogHeader>
|
package/src/switcher.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useState } from "react";
|
|
2
2
|
import { StyleSheet } from "react-native";
|
|
3
|
+
import { Avatar } from "./avatar";
|
|
3
4
|
import { Icon } from "./icon";
|
|
4
5
|
import { MenuButton } from "./menu_button";
|
|
5
6
|
import { PressableHighlight } from "./pressable_highlight";
|
|
@@ -12,6 +13,15 @@ import { CONTROL_HEIGHT, CONTROL_RADIUS } from "./control_surface";
|
|
|
12
13
|
export interface SwitcherItem {
|
|
13
14
|
id: string;
|
|
14
15
|
label: string;
|
|
16
|
+
/**
|
|
17
|
+
* An identity image for this item. Optional, and optional PER ITEM rather than
|
|
18
|
+
* per switcher: the marks appear as soon as ONE item carries an image, and an
|
|
19
|
+
* item without one falls back to `Avatar`'s initials. Letting the mark appear
|
|
20
|
+
* only on the items that have a picture is the alternative, and it shifts every
|
|
21
|
+
* unmarked label sideways — so the one entry nobody has photographed is also the
|
|
22
|
+
* one that stops lining up with the rest of the list.
|
|
23
|
+
*/
|
|
24
|
+
imageUri?: string;
|
|
15
25
|
}
|
|
16
26
|
|
|
17
27
|
interface SwitcherProps {
|
|
@@ -61,17 +71,29 @@ export function Switcher(props: SwitcherProps) {
|
|
|
61
71
|
[currentId, onSelect],
|
|
62
72
|
);
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
// `minItemsToRender` can be lowered to 1 — a switcher that always shows is how a
|
|
75
|
+
// hard scope tells the reader which one they are in — so the empty set is the
|
|
76
|
+
// only one left to answer for, and there is nothing to render for it.
|
|
77
|
+
if (items.length === 0 || items.length < minItemsToRender) return null;
|
|
65
78
|
|
|
66
|
-
const current = items.find((item) => item.id === currentId);
|
|
67
|
-
const
|
|
79
|
+
const current = items.find((item) => item.id === currentId) ?? items[0];
|
|
80
|
+
const withMarks = items.some((item) => item.imageUri);
|
|
81
|
+
// The `md` rung is `CONTROL_CONTENT_HEIGHT` — what a 40px band seats, which
|
|
82
|
+
// both the trigger and a `MenuButton` row are. `square`, because the things a
|
|
83
|
+
// switcher switches BETWEEN are organizations, workspaces and projects; a disc
|
|
84
|
+
// would file them as people.
|
|
85
|
+
const mark = (item: SwitcherItem) =>
|
|
86
|
+
withMarks ? (
|
|
87
|
+
<Avatar shape="square" size="md" name={item.label} source={item.imageUri ? { uri: item.imageUri } : undefined} />
|
|
88
|
+
) : undefined;
|
|
68
89
|
|
|
69
90
|
return (
|
|
70
91
|
<Popover open={open} onOpenChange={setOpen} side={side} align={align}>
|
|
71
92
|
<PopoverTrigger>
|
|
72
93
|
<PressableHighlight focusRing style={[styles.trigger, { maxWidth: maxTriggerWidth }]}>
|
|
94
|
+
{mark(current)}
|
|
73
95
|
<Text size="sm" weight="medium" color="muted" numberOfLines={1}>
|
|
74
|
-
{label}
|
|
96
|
+
{current.label}
|
|
75
97
|
</Text>
|
|
76
98
|
<Icon name="chevrons-up-down" size={14} color={colors.zinc["500"]} />
|
|
77
99
|
</PressableHighlight>
|
|
@@ -81,6 +103,7 @@ export function Switcher(props: SwitcherProps) {
|
|
|
81
103
|
{items.map((item) => (
|
|
82
104
|
<MenuButton
|
|
83
105
|
key={item.id}
|
|
106
|
+
icon={mark(item)}
|
|
84
107
|
title={item.label}
|
|
85
108
|
onPress={() => handleSelect(item.id)}
|
|
86
109
|
right={
|