@lotics/ui 1.6.0 → 1.8.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 +18 -3
- package/src/alert.tsx +3 -0
- package/src/alert_row.tsx +81 -0
- package/src/bar_chart.tsx +45 -20
- package/src/card.tsx +14 -0
- package/src/cell_date_format.ts +15 -4
- package/src/chart_area.tsx +105 -0
- package/src/chart_bar.tsx +154 -0
- package/src/chart_internals.tsx +43 -0
- package/src/dialog.tsx +3 -0
- package/src/kpi_card.tsx +77 -0
- package/src/legend_item.tsx +47 -0
- package/src/metric.tsx +55 -2
- package/src/overlay_scope.ts +44 -0
- package/src/popover.tsx +3 -0
- package/src/section_card.tsx +68 -0
- package/src/spacing.ts +23 -0
- package/src/sparkline.tsx +85 -0
- package/src/stacked_progress_bar.tsx +65 -0
- package/src/text.css +20 -1
- package/src/theme.tsx +61 -0
- package/src/trend_chip.tsx +65 -0
- package/src/trend_footer.tsx +56 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { View, StyleSheet } from "react-native";
|
|
2
|
+
import { Text } from "./text";
|
|
3
|
+
import { colors } from "./colors";
|
|
4
|
+
|
|
5
|
+
export interface TrendChipProps {
|
|
6
|
+
/**
|
|
7
|
+
* Percent delta vs comparator. Positive = up, negative = down, 0 = flat.
|
|
8
|
+
* Convention: signed. `value={12}` → "↑ 12%". `value={-5}` → "↓ 5%".
|
|
9
|
+
*/
|
|
10
|
+
value: number;
|
|
11
|
+
/**
|
|
12
|
+
* Semantic override. By default, up = good (green) and down = bad (red).
|
|
13
|
+
* For metrics where down is good (e.g. response time, error rate), pass
|
|
14
|
+
* `goodDirection="down"` to flip the colors without changing the arrow.
|
|
15
|
+
*/
|
|
16
|
+
goodDirection?: "up" | "down";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Inline ±% chip that sits next to a metric. Answers "is this good?" — the
|
|
21
|
+
* single most common missing piece on dashboards that show only "what".
|
|
22
|
+
* Stripe, Mercury, Linear all ship this pattern.
|
|
23
|
+
*
|
|
24
|
+
* Color logic: trend direction × goodDirection. Up + good="up" = green.
|
|
25
|
+
* Up + good="down" = red. Symmetric for down. Flat = neutral.
|
|
26
|
+
*/
|
|
27
|
+
export function TrendChip(props: TrendChipProps) {
|
|
28
|
+
const { value, goodDirection = "up" } = props;
|
|
29
|
+
|
|
30
|
+
const direction = value > 0 ? "up" : value < 0 ? "down" : "flat";
|
|
31
|
+
const arrow = direction === "up" ? "↑" : direction === "down" ? "↓" : "→";
|
|
32
|
+
|
|
33
|
+
const isGood =
|
|
34
|
+
direction === "flat"
|
|
35
|
+
? null
|
|
36
|
+
: (direction === "up" && goodDirection === "up") ||
|
|
37
|
+
(direction === "down" && goodDirection === "down");
|
|
38
|
+
|
|
39
|
+
const tone = isGood === null ? "neutral" : isGood ? "good" : "bad";
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<View style={[styles.chip, styles[`chip_${tone}`]]}>
|
|
43
|
+
<Text size="xs" weight="medium" style={styles[`text_${tone}`]}>
|
|
44
|
+
{arrow} {Math.abs(value)}%
|
|
45
|
+
</Text>
|
|
46
|
+
</View>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const styles = StyleSheet.create({
|
|
51
|
+
chip: {
|
|
52
|
+
paddingHorizontal: 6,
|
|
53
|
+
paddingVertical: 2,
|
|
54
|
+
borderRadius: 4,
|
|
55
|
+
alignSelf: "flex-start",
|
|
56
|
+
},
|
|
57
|
+
// Tinted backgrounds (50-shade) keep the chip readable + non-shouty.
|
|
58
|
+
// Pairs with same-hue 700-shade text → 4.5+ contrast.
|
|
59
|
+
chip_good: { backgroundColor: colors.green[50] },
|
|
60
|
+
chip_bad: { backgroundColor: colors.red[50] },
|
|
61
|
+
chip_neutral: { backgroundColor: colors.zinc[100] },
|
|
62
|
+
text_good: { color: colors.green[700] },
|
|
63
|
+
text_bad: { color: colors.red[700] },
|
|
64
|
+
text_neutral: { color: colors.zinc[600] },
|
|
65
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TrendingDown, TrendingUp } from "lucide-react";
|
|
2
|
+
import { View, StyleSheet } from "react-native";
|
|
3
|
+
import { Text } from "./text";
|
|
4
|
+
import { colors } from "./colors";
|
|
5
|
+
import { SPACE } from "./spacing";
|
|
6
|
+
|
|
7
|
+
interface TrendFooterProps {
|
|
8
|
+
/** Signed percentage change. Positive = up, negative = down, 0 = no
|
|
9
|
+
* arrow. Caller is responsible for skipping the component entirely
|
|
10
|
+
* when there's no comparator (e.g., last-period base = 0). */
|
|
11
|
+
value: number;
|
|
12
|
+
/** Suffix after the percentage, e.g. "so với tháng trước". */
|
|
13
|
+
periodLabel: string;
|
|
14
|
+
/** Optional detail line below, e.g. "Tháng trước: 50.000.000 đ". */
|
|
15
|
+
detail?: string;
|
|
16
|
+
/** Override the up=green convention for metrics where down is good
|
|
17
|
+
* (response time, error rate). Defaults to `up`. */
|
|
18
|
+
goodDirection?: "up" | "down";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Footer caption that pairs the shadcn TrendingUp icon with a directional
|
|
23
|
+
* Vietnamese sentence — the "Tăng X% so với tháng trước" pattern at the
|
|
24
|
+
* bottom of every chart card on Stripe, Mercury, Linear.
|
|
25
|
+
*
|
|
26
|
+
* Goes inside `<SectionCard footer={...} />`. The Card adds the hairline
|
|
27
|
+
* divider above; this component owns only the icon + text composition.
|
|
28
|
+
*/
|
|
29
|
+
export function TrendFooter(props: TrendFooterProps) {
|
|
30
|
+
const { value, periodLabel, detail, goodDirection = "up" } = props;
|
|
31
|
+
const up = value > 0;
|
|
32
|
+
const flat = value === 0;
|
|
33
|
+
const isGood = flat ? null : (up && goodDirection === "up") || (!up && goodDirection === "down");
|
|
34
|
+
const color = isGood === null ? colors.zinc[600] : isGood ? colors.green[700] : colors.red[700];
|
|
35
|
+
const Icon = up ? TrendingUp : TrendingDown;
|
|
36
|
+
return (
|
|
37
|
+
<View style={styles.container}>
|
|
38
|
+
<View style={styles.row}>
|
|
39
|
+
<Icon size={16} color={color} />
|
|
40
|
+
<Text size="sm" weight="medium" style={{ color }}>
|
|
41
|
+
{up ? "Tăng" : "Giảm"} {Math.abs(value)}% {periodLabel}
|
|
42
|
+
</Text>
|
|
43
|
+
</View>
|
|
44
|
+
{detail && (
|
|
45
|
+
<Text size="xs" color="muted">
|
|
46
|
+
{detail}
|
|
47
|
+
</Text>
|
|
48
|
+
)}
|
|
49
|
+
</View>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const styles = StyleSheet.create({
|
|
54
|
+
container: { gap: SPACE.xs },
|
|
55
|
+
row: { flexDirection: "row", alignItems: "center", gap: SPACE.sm },
|
|
56
|
+
});
|