@lotics/ui 1.27.0 → 2.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/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @lotics/ui
2
+
3
+ Cross-platform (react-native-web) UI primitives with no Lotics domain coupling —
4
+ no i18n, analytics, or domain types. Consumed by `frontend`, `browser_extension`,
5
+ and custom-code apps. Lotics-specific compositions live in `frontend` or
6
+ `@lotics/ui-internal`, not here.
7
+
8
+ The package ships `.tsx` source directly (no build step); consumers import
9
+ subpaths, e.g. `import { Button } from "@lotics/ui/button"`.
10
+
11
+ ## Dev harness
12
+
13
+ A standalone Vite app for developing the primitives in isolation — no Expo app,
14
+ no backend.
15
+
16
+ ```bash
17
+ npm run dev # from packages/ui — serves http://localhost:5173
18
+ ```
19
+
20
+ It lives in `dev/`: `main.tsx` mounts a hash-routed sidebar, `registry.ts` lists
21
+ the showcase pages, and `dev/pages/*` are the per-primitive demos. `vite.config.ts`
22
+ wires the react-native-web aliases (the same ones a consuming app's bundler sets up).
23
+
24
+ To add a page: create `dev/pages/<name>.tsx` exporting a component (use the
25
+ `Page`/`Section`/`Row` helpers from `dev/showcase.tsx`) and add one entry to the
26
+ `pages` array in `dev/registry.ts`.
27
+
28
+ ## Checks
29
+
30
+ ```bash
31
+ npm run typecheck # tsgo (covers src + dev)
32
+ npm run lint # oxlint
33
+ npm run test # vitest
34
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/ui",
3
- "version": "1.27.0",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tokens": "./src/tokens.ts",
@@ -35,8 +35,6 @@
35
35
  "./stacked_progress_bar": "./src/stacked_progress_bar.tsx",
36
36
  "./legend_item": "./src/legend_item.tsx",
37
37
  "./trend_footer": "./src/trend_footer.tsx",
38
- "./chart_area": "./src/chart_area.tsx",
39
- "./chart_bar": "./src/chart_bar.tsx",
40
38
  "./spacing": "./src/spacing.ts",
41
39
  "./theme": "./src/theme.tsx",
42
40
  "./progress_bar": "./src/progress_bar.tsx",
@@ -57,7 +55,6 @@
57
55
  "./button": "./src/button.tsx",
58
56
  "./checkbox": "./src/checkbox.tsx",
59
57
  "./combobox": "./src/combobox.tsx",
60
- "./search_select": "./src/search_select.tsx",
61
58
  "./portal": "./src/portal.tsx",
62
59
  "./popover_nav": "./src/popover_nav.tsx",
63
60
  "./popover": "./src/popover.tsx",
@@ -68,7 +65,7 @@
68
65
  "./pressable_highlight": "./src/pressable_highlight.tsx",
69
66
  "./icon_button": "./src/icon_button.tsx",
70
67
  "./info_popover": "./src/info_popover.tsx",
71
- "./select_item": "./src/select_item.tsx",
68
+ "./card_select_item": "./src/card_select_item.tsx",
72
69
  "./badge": "./src/badge.tsx",
73
70
  "./divider": "./src/divider.tsx",
74
71
  "./spacer": "./src/spacer.tsx",
@@ -184,8 +181,7 @@
184
181
  "react-dom": "^19.2.0",
185
182
  "react-native": ">=0.85.0",
186
183
  "react-native-svg": ">=15.0.0",
187
- "react-native-web": ">=0.20.0",
188
- "recharts": ">=3.0.0"
184
+ "react-native-web": ">=0.20.0"
189
185
  },
190
186
  "peerDependenciesMeta": {
191
187
  "@lotics/docx": {
@@ -205,12 +201,10 @@
205
201
  },
206
202
  "react-native-svg": {
207
203
  "optional": true
208
- },
209
- "recharts": {
210
- "optional": true
211
204
  }
212
205
  },
213
206
  "scripts": {
207
+ "dev": "vite",
214
208
  "typecheck": "tsgo --noEmit",
215
209
  "lint": "oxlint",
216
210
  "test": "vitest run"
@@ -218,6 +212,11 @@
218
212
  "devDependencies": {
219
213
  "@lotics/docx": "^0.1.0",
220
214
  "@lotics/xlsx": "^0.1.0",
221
- "recharts": "^3.8.1"
215
+ "@types/react-dom": "~19.2.2",
216
+ "@vitejs/plugin-react": "^4.3.4",
217
+ "lucide-react": "^0.562.0",
218
+ "react-dom": "^19.2.0",
219
+ "react-native-web": "^0.21.0",
220
+ "vite": "^7.2.4"
222
221
  }
223
222
  }
package/src/card.tsx CHANGED
@@ -1,32 +1,19 @@
1
- import { Pressable, StyleProp, StyleSheet, View, ViewStyle } from "react-native";
1
+ import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
2
2
  import { colors } from "./colors";
3
3
 
4
4
  interface CardProps {
5
5
  children: React.ReactNode;
6
6
  testID?: string;
7
- onPress?: () => void;
8
7
  style?: StyleProp<ViewStyle>;
9
8
  }
10
9
 
10
+ /**
11
+ * A bordered presentational surface — view only. It never handles interaction:
12
+ * for a pressable/selectable card use CardSelectItem (a real focusable button),
13
+ * or compose PressableHighlight for a bespoke action.
14
+ */
11
15
  export function Card(props: CardProps) {
12
- const { children, testID, onPress, style } = props;
13
-
14
- if (onPress) {
15
- return (
16
- <Pressable
17
- testID={testID}
18
- onPress={() => {
19
- onPress();
20
- }}
21
- style={(state) => {
22
- const hovered = (state as { hovered?: boolean }).hovered;
23
- return [styles.container, hovered && styles.hovered, style];
24
- }}
25
- >
26
- {children}
27
- </Pressable>
28
- );
29
- }
16
+ const { children, testID, style } = props;
30
17
 
31
18
  return (
32
19
  <View testID={testID} style={[styles.container, style]}>
@@ -55,7 +42,4 @@ const styles = StyleSheet.create({
55
42
  "0 1px 2px 0 rgba(38,38,38,0.06), 0 4px 12px -2px rgba(38,38,38,0.06)",
56
43
  } as ViewStyle),
57
44
  },
58
- hovered: {
59
- borderColor: colors.zinc["900"],
60
- },
61
45
  });
@@ -0,0 +1,52 @@
1
+ import { PressableStateCallbackType, StyleProp, StyleSheet, ViewStyle } from "react-native";
2
+ import { colors } from "./colors";
3
+ import { PressableHighlight } from "./pressable_highlight";
4
+
5
+ interface CardSelectItemProps {
6
+ children: React.ReactNode;
7
+ onPress: () => void;
8
+ testID?: string;
9
+ style?: StyleProp<ViewStyle>;
10
+ }
11
+
12
+ /**
13
+ * A bordered, card-shaped button — the selectable row used on auth screens
14
+ * (organization picker, login choices). Always interactive: a real focusable
15
+ * `button` that shows a 2px ring on hover/press, matching the global keyboard
16
+ * focus ring. For a static surface use Card instead.
17
+ */
18
+ export function CardSelectItem(props: CardSelectItemProps) {
19
+ const { children, onPress, testID, style } = props;
20
+
21
+ return (
22
+ <PressableHighlight
23
+ testID={testID}
24
+ accessibilityRole="button"
25
+ onPress={onPress}
26
+ style={(state: PressableStateCallbackType) => {
27
+ const hovered = (state as { hovered?: boolean }).hovered;
28
+ const active = hovered || state.pressed;
29
+ return [styles.container, active && styles.ring, style];
30
+ }}
31
+ >
32
+ {children}
33
+ </PressableHighlight>
34
+ );
35
+ }
36
+
37
+ const styles = StyleSheet.create({
38
+ container: {
39
+ padding: 16,
40
+ borderRadius: 8,
41
+ backgroundColor: colors.background,
42
+ borderColor: colors.border,
43
+ borderWidth: 1,
44
+ },
45
+ // A 2px ring flush against the border (spread 2, no offset/blur) — the same
46
+ // weight and color as the global `:focus-visible` outline, so hover, press,
47
+ // and keyboard focus all read as one consistent ring rather than a thin
48
+ // border-darken. boxShadow keeps it layout-neutral (no 1px→2px reflow).
49
+ ring: {
50
+ ...({ boxShadow: `0 0 0 2px ${colors.zinc["900"]}` } as ViewStyle),
51
+ },
52
+ });
@@ -155,7 +155,6 @@ export function ColumnFilter(props: ColumnFilterProps) {
155
155
  ) : (
156
156
  <PickerMenu
157
157
  multi
158
- enableSearch
159
158
  options={column.options ?? []}
160
159
  value={value?.kind === "select" ? value.selected : []}
161
160
  onValueChange={(selected) => onChange({ kind: "select", selected })}