@lotics/ui 18.2.0 → 19.0.1
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/MIGRATION.md +65 -0
- package/docs/catalog.md +78 -26
- package/docs/data_entry.md +55 -21
- package/docs/templates.md +17 -17
- package/examples/tpl_item_list.tsx +26 -26
- package/examples/tpl_record.tsx +38 -38
- package/package.json +1 -1
- package/src/detail_row.tsx +7 -1
- package/src/linked_record_box.tsx +30 -13
- package/src/sources.tsx +11 -1
- package/src/task.tsx +347 -135
- package/src/task_metrics.test.ts +48 -0
- package/src/task_metrics.ts +55 -0
package/package.json
CHANGED
package/src/detail_row.tsx
CHANGED
|
@@ -19,6 +19,12 @@ const DetailTableContext = createContext<DetailTableColumns | null>(null);
|
|
|
19
19
|
* side-by-side columns and stacks — below it, editors get crushed. */
|
|
20
20
|
const MIN_VALUE_WIDTH = 160;
|
|
21
21
|
|
|
22
|
+
/** THE kit's label column — ~18 characters, where real field names live. It is
|
|
23
|
+
* this table's default AND the floor a `TaskSubRow`'s label sizes from, so a
|
|
24
|
+
* record's fields and a task's read as one language on a surface carrying
|
|
25
|
+
* both; that only holds while the two are ONE number. */
|
|
26
|
+
export const DETAIL_LABEL_WIDTH = 130;
|
|
27
|
+
|
|
22
28
|
export interface DetailTableProps {
|
|
23
29
|
/** Label column width (px). Default 130. */
|
|
24
30
|
labelWidth?: number;
|
|
@@ -64,7 +70,7 @@ export interface DetailTableProps {
|
|
|
64
70
|
* the switch is automatic.
|
|
65
71
|
*/
|
|
66
72
|
export function DetailTable(props: DetailTableProps) {
|
|
67
|
-
const { labelWidth =
|
|
73
|
+
const { labelWidth = DETAIL_LABEL_WIDTH, trailingWidth, minHeight = INLINE_CONTROL_HEIGHT, minValueWidth = MIN_VALUE_WIDTH, children, style } = props;
|
|
68
74
|
const [width, setWidth] = useState<number | null>(null);
|
|
69
75
|
const stacked =
|
|
70
76
|
width != null && width < labelWidth + (trailingWidth ?? 0) + minValueWidth + 24;
|
|
@@ -5,6 +5,7 @@ import { PressDoor } from "./press_door";
|
|
|
5
5
|
import { Text } from "./text";
|
|
6
6
|
import { Icon, type IconName } from "./icon";
|
|
7
7
|
import { colors } from "./colors";
|
|
8
|
+
import { INLINE_CONTROL_HEIGHT } from "./inline_edit";
|
|
8
9
|
|
|
9
10
|
/** One reference fact. `V` is the value the variant admits — text under a door, any node without one. */
|
|
10
11
|
interface LinkedRecordFact<V = ReactNode> {
|
|
@@ -94,18 +95,30 @@ export function LinkedRecordBox(props: LinkedRecordBoxProps) {
|
|
|
94
95
|
</View>
|
|
95
96
|
{facts.length > 0 ? (
|
|
96
97
|
<View style={{ gap: 6 }}>
|
|
97
|
-
{facts.map((f) =>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
{facts.map((f) => {
|
|
99
|
+
// THE ALIGNMENT LAW, the same one `DetailRow` runs on: the fact TOP-aligns and each
|
|
100
|
+
// cell centres within the first CONTROL LINE. A node value brings the kit's control
|
|
101
|
+
// band (an inline editor is 40 tall, a chip less), so both cells claim it — without
|
|
102
|
+
// it the label `Text`, stretched to the row, printed its words at the band's TOP,
|
|
103
|
+
// a half-band above the value it names. A STRING value has no band: label and value
|
|
104
|
+
// are the same 20px line, so they sit at the row's top and a long value that wraps
|
|
105
|
+
// still tops out level with its label.
|
|
106
|
+
const banded = typeof f.value !== "string";
|
|
107
|
+
return (
|
|
108
|
+
<View key={f.label} style={styles.fact}>
|
|
109
|
+
<View style={[{ width: factLabelWidth }, banded && styles.bandedCell]}>
|
|
110
|
+
<Text size="sm" color="muted">{f.label}</Text>
|
|
111
|
+
</View>
|
|
112
|
+
{banded ? (
|
|
113
|
+
// A node prints as authored — the "—" placeholder is a TEXT affordance, and a
|
|
114
|
+
// control renders its own empty state.
|
|
115
|
+
<View style={[{ flex: 1, minWidth: 0 }, styles.bandedCell]}>{f.value}</View>
|
|
116
|
+
) : (
|
|
117
|
+
<Text size="sm" style={{ flex: 1 }}>{f.value || "—"}</Text>
|
|
118
|
+
)}
|
|
119
|
+
</View>
|
|
120
|
+
);
|
|
121
|
+
})}
|
|
109
122
|
</View>
|
|
110
123
|
) : null}
|
|
111
124
|
{actions ? (
|
|
@@ -134,7 +147,11 @@ const styles = StyleSheet.create({
|
|
|
134
147
|
box: { flexDirection: "column", alignItems: "stretch", borderWidth: 1, borderColor: colors.zinc[200], borderRadius: 10, paddingHorizontal: 16, paddingTop: 14, paddingBottom: 10, gap: 12 },
|
|
135
148
|
identity: { flexDirection: "row", alignItems: "center", gap: 10 },
|
|
136
149
|
glyph: { width: 34, height: 34, borderRadius: 8, backgroundColor: colors.zinc[100], alignItems: "center", justifyContent: "center" },
|
|
137
|
-
fact: { flexDirection: "row", gap: 10 },
|
|
150
|
+
fact: { flexDirection: "row", gap: 10, alignItems: "flex-start" },
|
|
151
|
+
// A cell on the control band: exactly `minHeight` tall for a one-line label / a short chip,
|
|
152
|
+
// and taller content (a wrapped label, a stack of controls) tops out level instead of being
|
|
153
|
+
// dragged toward the block's centre.
|
|
154
|
+
bandedCell: { minHeight: INLINE_CONTROL_HEIGHT, justifyContent: "center" },
|
|
138
155
|
divider: { height: 1, backgroundColor: colors.zinc[100], marginHorizontal: -16 },
|
|
139
156
|
actions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", columnGap: 8, rowGap: 8, zIndex: 1 },
|
|
140
157
|
});
|
package/src/sources.tsx
CHANGED
|
@@ -75,7 +75,7 @@ function SourceChip({ source, openable }: { source: SourceRef; openable?: boolea
|
|
|
75
75
|
<View style={[styles.iconDisc, { backgroundColor: tint(k.color, 0.14) }]}>
|
|
76
76
|
<Icon name={k.icon} size={13} color={solid(k.color)} />
|
|
77
77
|
</View>
|
|
78
|
-
<Text size="xs" weight="medium" numberOfLines={1}>
|
|
78
|
+
<Text size="xs" weight="medium" numberOfLines={1} style={styles.label}>
|
|
79
79
|
{source.label}
|
|
80
80
|
</Text>
|
|
81
81
|
{source.detail ? (
|
|
@@ -93,6 +93,14 @@ const styles = StyleSheet.create({
|
|
|
93
93
|
chip: {
|
|
94
94
|
flexDirection: "row",
|
|
95
95
|
alignItems: "center",
|
|
96
|
+
// A source label is a document TITLE and can be long — "Thành phần hồ sơ
|
|
97
|
+
// mua NOXH — Khoản 7 — Sĩ quan, hạ sĩ quan, quân nhân chuyên nghiệp". The
|
|
98
|
+
// chip must never exceed its row: `numberOfLines` alone cannot truncate a
|
|
99
|
+
// flex child that is free to grow, so the chip is capped and the label is
|
|
100
|
+
// the part that gives way (`flexShrink`), keeping the icon, the locator and
|
|
101
|
+
// the open glyph intact — they are what make the chip readable once the
|
|
102
|
+
// title is clipped.
|
|
103
|
+
maxWidth: "100%",
|
|
96
104
|
gap: 8,
|
|
97
105
|
paddingLeft: 5,
|
|
98
106
|
paddingRight: 10,
|
|
@@ -102,8 +110,10 @@ const styles = StyleSheet.create({
|
|
|
102
110
|
borderColor: colors.border,
|
|
103
111
|
backgroundColor: colors.white,
|
|
104
112
|
},
|
|
113
|
+
label: { flexShrink: 1 },
|
|
105
114
|
iconDisc: {
|
|
106
115
|
width: 24,
|
|
116
|
+
flexShrink: 0,
|
|
107
117
|
height: 24,
|
|
108
118
|
borderRadius: 7,
|
|
109
119
|
alignItems: "center",
|