@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "18.2.0",
3
+ "version": "19.0.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./vite": {
@@ -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 = 130, trailingWidth, minHeight = INLINE_CONTROL_HEIGHT, minValueWidth = MIN_VALUE_WIDTH, children, style } = props;
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
- <View key={f.label} style={styles.fact}>
99
- <Text size="sm" color="muted" style={{ width: factLabelWidth }}>{f.label}</Text>
100
- {typeof f.value === "string" ? (
101
- <Text size="sm" style={{ flex: 1 }}>{f.value || "—"}</Text>
102
- ) : (
103
- // A node prints as authored the "—" placeholder is a TEXT affordance, and a
104
- // control renders its own empty state.
105
- <View style={{ flex: 1, minWidth: 0 }}>{f.value}</View>
106
- )}
107
- </View>
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",