@lotics/ui 18.2.0 → 19.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "18.2.0",
3
+ "version": "19.0.0",
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
  });