@lotics/ui 47.1.0 → 47.2.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 +10 -0
- package/docs/templates.md +5 -2
- package/examples/tpl_item_list.tsx +21 -20
- package/package.json +2 -1
- package/src/filter_band.tsx +103 -0
- package/src/filter_chip.tsx +58 -0
- package/src/inline_edit.tsx +12 -2
- package/src/locale.tsx +3 -0
- package/src/number_input.tsx +7 -0
- package/src/text_input_field.tsx +6 -0
package/docs/catalog.md
CHANGED
|
@@ -1712,6 +1712,16 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1712
1712
|
- **`column_filter`** — `ColumnFilter` + `columnFilterToConditions` +
|
|
1713
1713
|
`isColumnFilterActive` + `columnFilterSummary`: the typed per-column filter pill; for a
|
|
1714
1714
|
register filtering on several columns.
|
|
1715
|
+
- **`filter_band`** — `FilterBand`: the toolbar ROW above a list — search, the `FilterChip`s,
|
|
1716
|
+
and the screen's primary action. **On a small screen the chips collapse into one `Filters (n)`
|
|
1717
|
+
button** that opens a sheet where the same chips render as full-width disclosure rows, one
|
|
1718
|
+
editor open at a time. Laid out inline, a search box plus five chips plus a segmented control
|
|
1719
|
+
is three wrapped rows; on a 390px phone that is most of the viewport spent before the first
|
|
1720
|
+
record. Search and the action never collapse — both are on the shortest path to what someone
|
|
1721
|
+
opened the screen for. Pass `activeCount` so the collapsed button still says the list is
|
|
1722
|
+
filtered, and `onClearAll` to put one clear in the sheet. Compose the band with this rather
|
|
1723
|
+
than a hand-rolled `<View flexDirection="row">`: a hand-rolled band is the one that does not
|
|
1724
|
+
collapse.
|
|
1715
1725
|
- **`filter_chip`** — `FilterChip` + `selectSummary`: the toolbar filter pill hosting a
|
|
1716
1726
|
facet (options, a `Slider range`, a `Counter`). It draws NO bottom band of its own — the ×
|
|
1717
1727
|
on the pill is the clear, and the editor inside brings whatever actions it has, so a
|
package/docs/templates.md
CHANGED
|
@@ -386,8 +386,11 @@ The corollaries, each of which a register is routinely missing:
|
|
|
386
386
|
information — a badge that shows nothing for a row missing its invoice is exactly the column
|
|
387
387
|
that earns its width.
|
|
388
388
|
|
|
389
|
-
- **One toolbar row
|
|
390
|
-
|
|
389
|
+
- **One toolbar row, built with `FilterBand`** — search + a status `Select` + facet `FilterChip`s LEFT,
|
|
390
|
+
the New CTA RIGHT. **Use `FilterBand` rather than a hand-rolled `<View flexDirection="row">`**: on a
|
|
391
|
+
phone it collapses the facets into one `Filters (n)` button and a sheet, and a hand-rolled band is the
|
|
392
|
+
one that does not — inline, this row is three wrapped lines at 390px and eats the screen before the
|
|
393
|
+
first record. Search and the CTA stay on the band at every width; then a light `SummaryLine` of the filtered view BELOW it, above the rows.
|
|
391
394
|
**Search is ALWAYS the leftmost control**, and the order after it is stated rather than left to
|
|
392
395
|
the author: search, then the scope the rest of the row filters WITHIN (a site, an account, a
|
|
393
396
|
period), then the facets. Search is the one control every register carries and the one a reader
|
|
@@ -28,6 +28,7 @@ import { Pagination } from "@lotics/ui/pagination";
|
|
|
28
28
|
import { Table, TableRow, TableCell, type TableColumn } from "@lotics/ui/table";
|
|
29
29
|
import { SearchInput } from "@lotics/ui/search_input";
|
|
30
30
|
import { Select } from "@lotics/ui/select";
|
|
31
|
+
import { FilterBand } from "@lotics/ui/filter_band";
|
|
31
32
|
import { FilterChip, selectSummary } from "@lotics/ui/filter_chip";
|
|
32
33
|
import { ColumnFilter, type ColumnFilterValue } from "@lotics/ui/column_filter";
|
|
33
34
|
import { OptionList } from "@lotics/ui/option_list";
|
|
@@ -1245,21 +1246,25 @@ export function TplItemList() {
|
|
|
1245
1246
|
|
|
1246
1247
|
{/* toolbar — search + consolidated filters LEFT, New case RIGHT, one row.
|
|
1247
1248
|
A search is present, so status is a dropdown (Select), not pills.
|
|
1248
|
-
|
|
1249
|
-
`
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1249
|
+
`FilterBand` owns the row. On a phone it collapses the filters into a
|
|
1250
|
+
single `Filters (n)` button and a sheet, so search and the CTA stay on
|
|
1251
|
+
one line. This row used to wrap to two or three lines there, which also
|
|
1252
|
+
left the CTA anchored to nothing — centred, it floated against the whole
|
|
1253
|
+
wrapped block and landed level with the SECOND row of chips. Collapsing
|
|
1254
|
+
the filters removes the wrap, and the alignment question with it. */}
|
|
1255
|
+
<FilterBand
|
|
1256
|
+
activeCount={[assignee.length > 0, feeStatus != null, feeAmount != null].filter(Boolean).length}
|
|
1257
|
+
onClearAll={() => { setAssignee([]); setFeeStatus(null); setFeeAmount(undefined); resetPaging(); }}
|
|
1258
|
+
action={<Button title="Enter data" color="primary" onPress={openIntake} />}
|
|
1259
|
+
search={
|
|
1260
|
+
<SearchInput
|
|
1261
|
+
placeholder="Search by ID, name, or phone…"
|
|
1262
|
+
value={search}
|
|
1263
|
+
onChangeText={doiTimKiem}
|
|
1264
|
+
accessibilityLabel="Search records"
|
|
1265
|
+
/>
|
|
1266
|
+
}
|
|
1267
|
+
>
|
|
1263
1268
|
<Select
|
|
1264
1269
|
placeholder="Status"
|
|
1265
1270
|
options={TABS.map((t) => ({
|
|
@@ -1310,11 +1315,7 @@ export function TplItemList() {
|
|
|
1310
1315
|
value={feeAmount}
|
|
1311
1316
|
onChange={(v) => { setFeeAmount(v); resetPaging(); }}
|
|
1312
1317
|
/>
|
|
1313
|
-
|
|
1314
|
-
{/* the intake TRIGGER — the register's whole-surface drop/paste opens
|
|
1315
|
-
the same dialog (see the FileDropTarget wrapper below) */}
|
|
1316
|
-
<Button title="Enter data" color="primary" onPress={openIntake} />
|
|
1317
|
-
</View>
|
|
1318
|
+
</FilterBand>
|
|
1318
1319
|
|
|
1319
1320
|
{/* THE SUMMARY READS THE FILTERED SET, so it sits BELOW the controls that
|
|
1320
1321
|
produce it. These figures move every time the reader touches a chip:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/ui",
|
|
3
|
-
"version": "47.
|
|
3
|
+
"version": "47.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./accordion": "./src/accordion.tsx",
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
"./file_thumbnail": "./src/file_thumbnail.tsx",
|
|
114
114
|
"./file_thumbnail_grid": "./src/file_thumbnail_grid.tsx",
|
|
115
115
|
"./files_editor": "./src/files_editor.tsx",
|
|
116
|
+
"./filter_band": "./src/filter_band.tsx",
|
|
116
117
|
"./filter_chip": "./src/filter_chip.tsx",
|
|
117
118
|
"./finding": "./src/finding.tsx",
|
|
118
119
|
"./floating_action_bar": "./src/floating_action_bar.tsx",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createContext, useContext, useState, type ReactNode } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import { Button } from "./button";
|
|
4
|
+
import { Dialog, DialogFooter, DialogHeader, DialogHeaderTitle, DialogScrollArea } from "./dialog";
|
|
5
|
+
import { SPACE } from "./spacing";
|
|
6
|
+
import { useLoticsLocale } from "./locale";
|
|
7
|
+
import { useScreenSize } from "./use_screen_size";
|
|
8
|
+
|
|
9
|
+
interface FilterBandContextValue {
|
|
10
|
+
/** How a `FilterChip` inside this band should render itself. */
|
|
11
|
+
presentation: "chip" | "row";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const FilterBandContext = createContext<FilterBandContextValue | null>(null);
|
|
15
|
+
|
|
16
|
+
/** How the nearest enclosing `FilterBand` wants its filters drawn. `"chip"`
|
|
17
|
+
* outside a band, so a lone `FilterChip` is unaffected. */
|
|
18
|
+
export function useFilterBandPresentation(): "chip" | "row" {
|
|
19
|
+
return useContext(FilterBandContext)?.presentation ?? "chip";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FilterBandProps {
|
|
23
|
+
/** The search control. Stays on the band at every width — it is the one
|
|
24
|
+
* filter people reach for without thinking. */
|
|
25
|
+
search?: ReactNode;
|
|
26
|
+
/** The band's primary action. Stays visible at every width, because a screen
|
|
27
|
+
* whose only verb is behind a filter sheet reads as read-only. */
|
|
28
|
+
action?: ReactNode;
|
|
29
|
+
/** How many dimensions are filtering right now — badges the collapsed
|
|
30
|
+
* button, so a narrow screen still says the list is not showing everything. */
|
|
31
|
+
activeCount?: number;
|
|
32
|
+
/** Clears every dimension at once. Rendered inside the sheet when given. */
|
|
33
|
+
onClearAll?: () => void;
|
|
34
|
+
/** The `FilterChip`s. */
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
testID?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The toolbar row above a list: search, the secondary filter dimensions, and
|
|
41
|
+
* the screen's primary action.
|
|
42
|
+
*
|
|
43
|
+
* **On a narrow screen the dimensions collapse into ONE button.** Laid out
|
|
44
|
+
* inline, a search box plus five chips plus a segmented control is three
|
|
45
|
+
* wrapped rows — on a 390px phone that is most of the viewport spent before a
|
|
46
|
+
* single record, which is what this component exists to prevent. Collapsed, the
|
|
47
|
+
* band is one row: search, a `Filters (n)` button, and the action. The chips
|
|
48
|
+
* move into a sheet and render as full-width rows there, one editor open at a
|
|
49
|
+
* time — the SAME `FilterChip`s, re-presented, so a filter never has two
|
|
50
|
+
* implementations to keep in step.
|
|
51
|
+
*
|
|
52
|
+
* Search and the action never collapse. Both are one tap on the shortest path
|
|
53
|
+
* to what someone opened the screen for, and burying either behind a sheet
|
|
54
|
+
* costs more than the row it saves.
|
|
55
|
+
*/
|
|
56
|
+
export function FilterBand(props: FilterBandProps) {
|
|
57
|
+
const { search, action, activeCount = 0, onClearAll, children, testID } = props;
|
|
58
|
+
const t = useLoticsLocale().filterBand;
|
|
59
|
+
const { small } = useScreenSize();
|
|
60
|
+
const [open, setOpen] = useState(false);
|
|
61
|
+
|
|
62
|
+
if (!small) {
|
|
63
|
+
return (
|
|
64
|
+
<View
|
|
65
|
+
testID={testID}
|
|
66
|
+
style={{ flexDirection: "row", alignItems: "center", gap: SPACE.sm, flexWrap: "wrap" }}
|
|
67
|
+
>
|
|
68
|
+
{search != null && (
|
|
69
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: 220, minWidth: 180, maxWidth: 320 }}>{search}</View>
|
|
70
|
+
)}
|
|
71
|
+
<FilterBandContext.Provider value={{ presentation: "chip" }}>{children}</FilterBandContext.Provider>
|
|
72
|
+
{action != null && <View style={{ marginLeft: "auto" }}>{action}</View>}
|
|
73
|
+
</View>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<View testID={testID} style={{ flexDirection: "row", alignItems: "center", gap: SPACE.sm }}>
|
|
79
|
+
{search != null && <View style={{ flexGrow: 1, flexShrink: 1, minWidth: 0 }}>{search}</View>}
|
|
80
|
+
<Button
|
|
81
|
+
color="secondary"
|
|
82
|
+
title={activeCount > 0 ? `${t.filters} (${activeCount})` : t.filters}
|
|
83
|
+
onPress={() => setOpen(true)}
|
|
84
|
+
testID={testID ? `${testID}-filters` : undefined}
|
|
85
|
+
/>
|
|
86
|
+
{action}
|
|
87
|
+
<Dialog open={open} onOpenChange={setOpen} width="92%" maxWidth={520}>
|
|
88
|
+
<DialogHeader>
|
|
89
|
+
<DialogHeaderTitle>{t.filters}</DialogHeaderTitle>
|
|
90
|
+
</DialogHeader>
|
|
91
|
+
<DialogScrollArea>
|
|
92
|
+
<FilterBandContext.Provider value={{ presentation: "row" }}>{children}</FilterBandContext.Provider>
|
|
93
|
+
</DialogScrollArea>
|
|
94
|
+
<DialogFooter>
|
|
95
|
+
{onClearAll != null && activeCount > 0 && (
|
|
96
|
+
<Button color="muted" title={t.clearAll} onPress={onClearAll} />
|
|
97
|
+
)}
|
|
98
|
+
<Button title={t.done} onPress={() => setOpen(false)} />
|
|
99
|
+
</DialogFooter>
|
|
100
|
+
</Dialog>
|
|
101
|
+
</View>
|
|
102
|
+
);
|
|
103
|
+
}
|
package/src/filter_chip.tsx
CHANGED
|
@@ -7,6 +7,8 @@ import { Chip } from "./chip";
|
|
|
7
7
|
import { Popover, PopoverTrigger, PopoverContent, PopoverFooter } from "./popover";
|
|
8
8
|
import type { PopoverSide, PopoverAlign } from "./popover";
|
|
9
9
|
import { useLoticsLocale } from "./locale";
|
|
10
|
+
import { useFilterBandPresentation } from "./filter_band";
|
|
11
|
+
import { Pressable } from "react-native";
|
|
10
12
|
|
|
11
13
|
export interface FilterChipProps {
|
|
12
14
|
/** The dimension name — shown alone when inactive ("Owner"), prefixed when
|
|
@@ -103,6 +105,7 @@ export function FilterChip(props: FilterChipProps) {
|
|
|
103
105
|
// Own the open state so the editor can close itself via the render-prop
|
|
104
106
|
// `close`, while still honoring a controlled `open`/`onOpenChange` from the
|
|
105
107
|
// parent (the Save-footer VALUE-pill case).
|
|
108
|
+
const presentation = useFilterBandPresentation();
|
|
106
109
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
107
110
|
const isOpen = open ?? internalOpen;
|
|
108
111
|
const setOpen = (next: boolean) => {
|
|
@@ -110,6 +113,41 @@ export function FilterChip(props: FilterChipProps) {
|
|
|
110
113
|
onOpenChange?.(next);
|
|
111
114
|
};
|
|
112
115
|
|
|
116
|
+
// Inside a COLLAPSED `FilterBand` the pill is the wrong shape: a sheet is a
|
|
117
|
+
// vertical list, and a row of pills in it wraps exactly the way the band did
|
|
118
|
+
// on the screen this collapse exists to fix. Same component, same state, same
|
|
119
|
+
// editor — re-presented as a full-width disclosure row.
|
|
120
|
+
if (presentation === "row") {
|
|
121
|
+
return (
|
|
122
|
+
<View style={styles.row}>
|
|
123
|
+
<Pressable
|
|
124
|
+
testID={testID}
|
|
125
|
+
onPress={() => setOpen(!isOpen)}
|
|
126
|
+
accessibilityRole="button"
|
|
127
|
+
accessibilityState={{ expanded: isOpen }}
|
|
128
|
+
style={styles.rowHeader}
|
|
129
|
+
>
|
|
130
|
+
<Text size="sm" weight="medium" color="zinc-700">{label}</Text>
|
|
131
|
+
<View style={styles.rowRight}>
|
|
132
|
+
{active ? (
|
|
133
|
+
typeof summary === "string" ? (
|
|
134
|
+
<Text size="sm" numberOfLines={1} style={{ color: colors.accent }}>{summary}</Text>
|
|
135
|
+
) : (
|
|
136
|
+
summary
|
|
137
|
+
)
|
|
138
|
+
) : null}
|
|
139
|
+
<Icon name={isOpen ? "chevron-up" : "chevron-down"} size={14} color={colors.zinc["400"]} />
|
|
140
|
+
</View>
|
|
141
|
+
</Pressable>
|
|
142
|
+
{isOpen ? (
|
|
143
|
+
<View style={styles.rowBody}>
|
|
144
|
+
{typeof children === "function" ? children({ close: () => setOpen(false) }) : children}
|
|
145
|
+
</View>
|
|
146
|
+
) : null}
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
113
151
|
return (
|
|
114
152
|
<Popover side={side} align={align} open={isOpen} onOpenChange={setOpen}>
|
|
115
153
|
<PopoverTrigger>
|
|
@@ -164,4 +202,24 @@ const styles = StyleSheet.create({
|
|
|
164
202
|
alignItems: "center",
|
|
165
203
|
gap: 5,
|
|
166
204
|
},
|
|
205
|
+
row: {
|
|
206
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
207
|
+
borderTopColor: colors.zinc["200"],
|
|
208
|
+
},
|
|
209
|
+
rowHeader: {
|
|
210
|
+
flexDirection: "row",
|
|
211
|
+
alignItems: "center",
|
|
212
|
+
justifyContent: "space-between",
|
|
213
|
+
gap: 12,
|
|
214
|
+
paddingVertical: 14,
|
|
215
|
+
},
|
|
216
|
+
rowRight: {
|
|
217
|
+
flexDirection: "row",
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
gap: 8,
|
|
220
|
+
flexShrink: 1,
|
|
221
|
+
},
|
|
222
|
+
rowBody: {
|
|
223
|
+
paddingBottom: 12,
|
|
224
|
+
},
|
|
167
225
|
});
|
package/src/inline_edit.tsx
CHANGED
|
@@ -823,7 +823,11 @@ const styles = StyleSheet.create({
|
|
|
823
823
|
// the row's own menu. With `marginLeft` alone the box grows leftward, its right
|
|
824
824
|
// edge stays on the container, and `paddingRight` still holds the text off the
|
|
825
825
|
// border.
|
|
826
|
-
|
|
826
|
+
// Stretches for the same reason `editRow` does — it is the outermost element
|
|
827
|
+
// when there are no verbs, so a right-aligned cell would size IT to content
|
|
828
|
+
// instead. The negative margin is the bare variant's deliberate 8px bleed, so
|
|
829
|
+
// the field reads 8px wider than its column by design.
|
|
830
|
+
viewBareBleed: { marginLeft: -8, alignSelf: "stretch", minWidth: 0 },
|
|
827
831
|
// The shell's own inset: the right edge tightens to 4 so an `InlineButton` sits
|
|
828
832
|
// on the surface's edge with the same air a `Chip`'s dismiss gets.
|
|
829
833
|
shell: { paddingRight: 4 },
|
|
@@ -864,7 +868,13 @@ const styles = StyleSheet.create({
|
|
|
864
868
|
// showed it — `Text` sets its own alignment — so the centring only surfaced
|
|
865
869
|
// once `display` accepted a node and rendered prose that inherited it.
|
|
866
870
|
viewNode: { flex: 1, minWidth: 0, textAlign: "left" },
|
|
867
|
-
|
|
871
|
+
// `alignSelf: stretch` because an inline edit takes its width from the COLUMN
|
|
872
|
+
// it sits in, never from what it happens to contain. Without it a right-aligned
|
|
873
|
+
// cell (`alignItems: flex-end`) sizes this row to its content, and in edit mode
|
|
874
|
+
// the content is a native `<input>`, whose default `size=20` is ~236px it will
|
|
875
|
+
// not shrink below. In a 104px quantity column that overflowed 132px LEFTWARD —
|
|
876
|
+
// pinned to the cell's right edge, spilling across the column beside it.
|
|
877
|
+
editRow: { flexDirection: "row", alignItems: "flex-start", gap: 6, alignSelf: "stretch", minWidth: 0 },
|
|
868
878
|
editControl: { flex: 1, position: "relative" },
|
|
869
879
|
// The input brings its own 40px height, so the surface must not add its resting
|
|
870
880
|
// vertical padding on top — that was a 12px jump the moment the field focused.
|
package/src/locale.tsx
CHANGED
|
@@ -68,6 +68,7 @@ export interface LoticsLocale {
|
|
|
68
68
|
/** `FilterChip` (and `ColumnFilter`): the generic clear affordance, used when
|
|
69
69
|
* a call site doesn't pass a dimension-specific `clearLabel`. */
|
|
70
70
|
filterChip: { clear: string };
|
|
71
|
+
filterBand: { filters: string; clearAll: string; done: string };
|
|
71
72
|
/** `FloatingActionBar`: the escape that drops the selection. The COUNT's noun is
|
|
72
73
|
* the call site's `label` (it names the rows), so only the escape lives here. */
|
|
73
74
|
floatingActionBar: { clear: string };
|
|
@@ -348,6 +349,7 @@ export const en: LoticsLocale = {
|
|
|
348
349
|
empty: "Nothing scheduled",
|
|
349
350
|
},
|
|
350
351
|
filterChip: { clear: "Clear" },
|
|
352
|
+
filterBand: { filters: "Filters", clearAll: "Clear all", done: "Done" },
|
|
351
353
|
floatingActionBar: { clear: "Clear" },
|
|
352
354
|
formField: { optional: "Optional" },
|
|
353
355
|
dangerZone: { title: "Danger zone" },
|
|
@@ -567,6 +569,7 @@ export const vi: LoticsLocale = {
|
|
|
567
569
|
empty: "Chưa có việc nào",
|
|
568
570
|
},
|
|
569
571
|
filterChip: { clear: "Xóa" },
|
|
572
|
+
filterBand: { filters: "Bộ lọc", clearAll: "Xoá lọc", done: "Xong" },
|
|
570
573
|
floatingActionBar: { clear: "Bỏ chọn" },
|
|
571
574
|
formField: { optional: "Tùy chọn" },
|
|
572
575
|
dangerZone: { title: "Vùng nguy hiểm" },
|
package/src/number_input.tsx
CHANGED
|
@@ -73,6 +73,13 @@ export function NumberInput(props: NumberInputProps) {
|
|
|
73
73
|
autoFocus={autoFocus}
|
|
74
74
|
style={{
|
|
75
75
|
height: 40,
|
|
76
|
+
// A native input carries `size=20` — about 236px of intrinsic width it
|
|
77
|
+
// refuses to shrink below, because a flex item's `min-width` defaults to
|
|
78
|
+
// `auto`. In any column narrower than that the field overflowed rather
|
|
79
|
+
// than fitting. Width comes from the container; the input never states
|
|
80
|
+
// its own.
|
|
81
|
+
width: "100%",
|
|
82
|
+
minWidth: 0,
|
|
76
83
|
paddingLeft: seamless ? 0 : 8,
|
|
77
84
|
paddingRight: seamless ? 0 : 8,
|
|
78
85
|
borderRadius: CONTROL_RADIUS,
|
package/src/text_input_field.tsx
CHANGED
|
@@ -291,6 +291,12 @@ const styles = StyleSheet.create({
|
|
|
291
291
|
// overrides via `style` (it lands after this in the array).
|
|
292
292
|
backgroundColor: colors.white,
|
|
293
293
|
height: 40,
|
|
294
|
+
// Same reason `NumberInput` states it: a native input carries `size=20`,
|
|
295
|
+
// about 236px of intrinsic width, and a flex item's `min-width` defaults to
|
|
296
|
+
// `auto` — so in any column narrower than that the field overflows instead
|
|
297
|
+
// of fitting. Width comes from the container; the input never states its own.
|
|
298
|
+
width: "100%",
|
|
299
|
+
minWidth: 0,
|
|
294
300
|
paddingVertical: INPUT_PADDING_Y,
|
|
295
301
|
paddingHorizontal: 8,
|
|
296
302
|
fontFamily: fontFamilyRegular,
|