@lotics/ui 1.5.1 → 1.6.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": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -31,6 +31,7 @@
31
31
  "./pressable_highlight": "./src/pressable_highlight.tsx",
32
32
  "./icon_button": "./src/icon_button.tsx",
33
33
  "./info_popover": "./src/info_popover.tsx",
34
+ "./select_item": "./src/select_item.tsx",
34
35
  "./badge": "./src/badge.tsx",
35
36
  "./divider": "./src/divider.tsx",
36
37
  "./spacer": "./src/spacer.tsx",
package/src/bar_chart.tsx CHANGED
@@ -90,10 +90,12 @@ export function BarChart(props: BarChartProps) {
90
90
 
91
91
  return (
92
92
  <View key={index} style={styles.horizontalBarContainer}>
93
- <Text size="sm" numberOfLines={1}>
94
- {item.label}
95
- </Text>
96
- <View style={styles.horizontalBarWrapper}>
93
+ <View style={styles.horizontalBarLabel}>
94
+ <Text size="sm" numberOfLines={1}>
95
+ {item.label}
96
+ </Text>
97
+ </View>
98
+ <View style={styles.horizontalBarTrack}>
97
99
  <View
98
100
  style={[
99
101
  styles.horizontalBar,
@@ -103,23 +105,31 @@ export function BarChart(props: BarChartProps) {
103
105
  },
104
106
  ]}
105
107
  />
106
- <Text size="sm">{formatNumber(item.value)}</Text>
108
+ </View>
109
+ <View style={styles.horizontalBarValue}>
110
+ <Text size="sm" numberOfLines={1}>
111
+ {formatNumber(item.value)}
112
+ </Text>
107
113
  </View>
108
114
  </View>
109
115
  );
110
116
  })}
111
117
  <View style={styles.horizontalAxisContainer}>
112
- {axisTicks.map((tick, index) => (
113
- <View
114
- key={index}
115
- style={[styles.horizontalAxisTick, { left: `${(tick / axisMax) * 100}%` }]}
116
- >
117
- <View style={styles.tickMark} />
118
- <Text size="xs" color="muted">
119
- {formatNumber(tick)}
120
- </Text>
121
- </View>
122
- ))}
118
+ <View style={styles.horizontalBarLabel} />
119
+ <View style={styles.horizontalAxisTrack}>
120
+ {axisTicks.map((tick, index) => (
121
+ <View
122
+ key={index}
123
+ style={[styles.horizontalAxisTick, { left: `${(tick / axisMax) * 100}%` }]}
124
+ >
125
+ <View style={styles.tickMark} />
126
+ <Text size="xs" color="muted">
127
+ {formatNumber(tick)}
128
+ </Text>
129
+ </View>
130
+ ))}
131
+ </View>
132
+ <View style={styles.horizontalBarValue} />
123
133
  </View>
124
134
  </View>
125
135
  </View>
@@ -239,14 +249,24 @@ const styles = StyleSheet.create({
239
249
  horizontalChart: {
240
250
  gap: 8,
241
251
  },
252
+ // One row per item: a fixed label column, the bar in a flexing track, and
253
+ // the value in a fixed column — so neither label nor value can be clipped
254
+ // by a long bar, and each item takes one compact row instead of two.
242
255
  horizontalBarContainer: {
243
- gap: 4,
244
- },
245
- horizontalBarWrapper: {
246
256
  flexDirection: "row",
247
257
  alignItems: "center",
248
258
  gap: 8,
249
259
  },
260
+ horizontalBarLabel: {
261
+ width: 80,
262
+ },
263
+ horizontalBarTrack: {
264
+ flex: 1,
265
+ },
266
+ horizontalBarValue: {
267
+ width: 96,
268
+ alignItems: "flex-end",
269
+ },
250
270
  horizontalBar: {
251
271
  backgroundColor: colors.blue[500],
252
272
  borderRadius: 2,
@@ -254,9 +274,14 @@ const styles = StyleSheet.create({
254
274
  minWidth: 2,
255
275
  },
256
276
  horizontalAxisContainer: {
277
+ flexDirection: "row",
278
+ gap: 8,
279
+ marginTop: 4,
280
+ },
281
+ horizontalAxisTrack: {
282
+ flex: 1,
257
283
  position: "relative",
258
284
  height: 24,
259
- marginTop: 4,
260
285
  },
261
286
  horizontalAxisTick: {
262
287
  position: "absolute",
package/src/card.tsx CHANGED
@@ -37,11 +37,9 @@ export function Card(props: CardProps) {
37
37
 
38
38
  const styles = StyleSheet.create({
39
39
  container: {
40
- padding: 16,
41
- borderRadius: 8,
40
+ padding: 20,
41
+ borderRadius: 16,
42
42
  backgroundColor: colors.background,
43
- borderColor: colors.border,
44
- borderWidth: 1,
45
43
  },
46
44
  hovered: {
47
45
  outlineColor: colors.zinc["900"],
package/src/metric.tsx CHANGED
@@ -5,6 +5,9 @@ import { useMemo } from "react";
5
5
 
6
6
  export type MetricFormat = "currency" | "number" | "percentage" | "none";
7
7
 
8
+ /** Semantic colour of the metric value. `default` inherits the text colour. */
9
+ export type MetricTone = "default" | "warning" | "danger";
10
+
8
11
  export interface MetricProps {
9
12
  value: number | string | null | undefined;
10
13
  previousValue?: number | string | null | undefined;
@@ -12,12 +15,18 @@ export interface MetricProps {
12
15
  currency?: string;
13
16
  locale?: string;
14
17
  emptyLabel?: string;
18
+ tone?: MetricTone;
15
19
  }
16
20
 
17
21
  const TREND_UP = "↑";
18
22
  const TREND_DOWN = "↓";
19
23
  const TREND_FLAT = "→";
20
24
 
25
+ const TONE_COLOR: Record<Exclude<MetricTone, "default">, string> = {
26
+ warning: colors.amber[600],
27
+ danger: colors.red[600],
28
+ };
29
+
21
30
  export function Metric(props: MetricProps) {
22
31
  const {
23
32
  value,
@@ -26,6 +35,7 @@ export function Metric(props: MetricProps) {
26
35
  currency = "USD",
27
36
  locale,
28
37
  emptyLabel = "-",
38
+ tone = "default",
29
39
  } = props;
30
40
 
31
41
  const displayValue = useMemo(() => {
@@ -55,7 +65,11 @@ export function Metric(props: MetricProps) {
55
65
 
56
66
  return (
57
67
  <View style={styles.container}>
58
- <Text size="xl" weight="semibold">
68
+ <Text
69
+ size="xl"
70
+ weight="semibold"
71
+ style={tone === "default" ? undefined : { color: TONE_COLOR[tone] }}
72
+ >
59
73
  {displayValue}
60
74
  </Text>
61
75
  {trend && (
@@ -0,0 +1,57 @@
1
+ import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
2
+ import { colors } from "./colors";
3
+
4
+ interface SelectItemProps {
5
+ children: React.ReactNode;
6
+ testID?: string;
7
+ onPress?: () => void;
8
+ style?: StyleProp<ViewStyle>;
9
+ }
10
+
11
+ /**
12
+ * A bordered, optionally-pressable container — the selectable-row look used on
13
+ * auth screens (organization picker, login choices). Frozen at the original
14
+ * Card styling (white, 1px border, radius 8, padding 16) so those screens stay
15
+ * visually stable while Card itself evolves toward a flatter dashboard look.
16
+ */
17
+ export function SelectItem(props: SelectItemProps) {
18
+ const { children, testID, onPress, style } = props;
19
+
20
+ if (onPress) {
21
+ return (
22
+ <Pressable
23
+ testID={testID}
24
+ onPress={() => {
25
+ onPress();
26
+ }}
27
+ style={(state) => {
28
+ const hovered = (state as { hovered?: boolean }).hovered;
29
+ return [styles.container, hovered && styles.hovered, style];
30
+ }}
31
+ >
32
+ {children}
33
+ </Pressable>
34
+ );
35
+ }
36
+
37
+ return (
38
+ <View testID={testID} style={[styles.container, style]}>
39
+ {children}
40
+ </View>
41
+ );
42
+ }
43
+
44
+ const styles = StyleSheet.create({
45
+ container: {
46
+ padding: 16,
47
+ borderRadius: 8,
48
+ backgroundColor: colors.background,
49
+ borderColor: colors.border,
50
+ borderWidth: 1,
51
+ },
52
+ hovered: {
53
+ outlineColor: colors.zinc["900"],
54
+ outlineWidth: 2,
55
+ outlineStyle: "solid",
56
+ },
57
+ });