@lotics/ui 2.6.1 → 3.1.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.
Files changed (46) hide show
  1. package/package.json +2 -15
  2. package/src/react_native.d.ts +2 -2
  3. package/src/segmented_control.tsx +201 -0
  4. package/src/cell_date.tsx +0 -30
  5. package/src/cell_date_format.test.ts +0 -32
  6. package/src/cell_date_format.ts +0 -73
  7. package/src/cell_number.test.ts +0 -42
  8. package/src/cell_number.tsx +0 -25
  9. package/src/cell_number_format.ts +0 -42
  10. package/src/cell_select.tsx +0 -68
  11. package/src/cell_text.tsx +0 -45
  12. package/src/grid/data_grid.tsx +0 -2003
  13. package/src/grid/data_grid_columns.test.ts +0 -72
  14. package/src/grid/data_grid_columns.ts +0 -30
  15. package/src/grid/data_grid_context.ts +0 -119
  16. package/src/grid/dispatch_safely.ts +0 -39
  17. package/src/grid/engine.module.css +0 -114
  18. package/src/grid/engine.tsx +0 -1042
  19. package/src/grid/helpers.ts +0 -205
  20. package/src/grid/layout.test.ts +0 -515
  21. package/src/grid/layout.ts +0 -425
  22. package/src/grid/recycling.test.ts +0 -236
  23. package/src/grid/recycling.ts +0 -172
  24. package/src/grid/row_cell.module.css +0 -105
  25. package/src/grid/row_cell.tsx +0 -313
  26. package/src/grid/search_highlight.ts +0 -71
  27. package/src/grid/select_cell.tsx +0 -58
  28. package/src/grid/select_group_summary_cell.tsx +0 -76
  29. package/src/grid/select_header_cell.tsx +0 -32
  30. package/src/grid/skeleton_row.module.css +0 -34
  31. package/src/grid/skeleton_row.tsx +0 -20
  32. package/src/grid/use_grid_groups.ts +0 -311
  33. package/src/grid/use_scroll_to_cell.ts +0 -135
  34. package/src/grid/use_virtual_grid.ts +0 -383
  35. package/src/grid/visibility.test.ts +0 -208
  36. package/src/grid/visibility.ts +0 -77
  37. package/src/kanban/constants.ts +0 -18
  38. package/src/kanban/default_renderers.tsx +0 -160
  39. package/src/kanban/drag_preview.tsx +0 -157
  40. package/src/kanban/index.ts +0 -13
  41. package/src/kanban/insert_card_zone.tsx +0 -135
  42. package/src/kanban/kanban_board.tsx +0 -635
  43. package/src/kanban/kanban_card.tsx +0 -321
  44. package/src/kanban/kanban_column.tsx +0 -499
  45. package/src/kanban/placeholders.tsx +0 -54
  46. package/src/kanban/types.ts +0 -116
@@ -1,160 +0,0 @@
1
- import { Pressable, StyleSheet, Text, View } from "react-native";
2
-
3
- import { Icon } from "../icon";
4
- import { colors } from "../colors";
5
-
6
- import {
7
- KanbanRenderAddCardPlaceholderProps,
8
- KanbanRenderCollapsedColumnProps,
9
- KanbanRenderColumnHeaderProps,
10
- } from "./types";
11
-
12
- export function defaultRenderColumnHeader({
13
- title,
14
- itemCount,
15
- isMinimized,
16
- onMinimizeToggle,
17
- }: KanbanRenderColumnHeaderProps): React.ReactNode {
18
- if (isMinimized) {
19
- return null;
20
- }
21
-
22
- return (
23
- <View style={styles.header}>
24
- <View style={styles.headerContent}>
25
- <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
26
- <Text style={styles.headerTitleText}>{title}</Text>
27
- <View style={styles.headerCount}>
28
- <Text style={styles.headerCountText}>{itemCount}</Text>
29
- </View>
30
- </View>
31
- <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
32
- <Pressable onPressIn={(e) => e.stopPropagation()} onPress={onMinimizeToggle} hitSlop={8}>
33
- <Icon name="minimize-2" size={16} color={colors.zinc[400]} />
34
- </Pressable>
35
- </View>
36
- </View>
37
- </View>
38
- );
39
- }
40
-
41
- export function defaultRenderAddCardPlaceholder({
42
- onPress,
43
- }: KanbanRenderAddCardPlaceholderProps): React.ReactNode {
44
- return (
45
- <Pressable style={styles.addCard} onPress={onPress}>
46
- <Icon name="plus" size={16} color={colors.zinc[400]} />
47
- <Text style={styles.addCardText}>Add card</Text>
48
- </Pressable>
49
- );
50
- }
51
-
52
- export function defaultRenderCollapsedColumn({
53
- title,
54
- itemCount,
55
- showDropIndicator,
56
- onExpand,
57
- }: KanbanRenderCollapsedColumnProps): React.ReactNode {
58
- return (
59
- <Pressable style={styles.collapsedColumn} onPress={onExpand}>
60
- <View style={styles.collapsedContent}>
61
- <View style={styles.collapsedCount}>
62
- <Text style={styles.collapsedCountText}>{itemCount}</Text>
63
- </View>
64
- <Text style={styles.collapsedTitle} numberOfLines={1}>
65
- {title}
66
- </Text>
67
- </View>
68
- {showDropIndicator && <View style={styles.collapsedDropIndicator} />}
69
- </Pressable>
70
- );
71
- }
72
-
73
- const styles = StyleSheet.create({
74
- header: {
75
- padding: 12,
76
- borderBottomWidth: 1,
77
- borderBottomColor: colors.zinc[200],
78
- },
79
- headerContent: {
80
- flexDirection: "row",
81
- alignItems: "center",
82
- justifyContent: "space-between",
83
- },
84
- headerTitleText: {
85
- fontWeight: "600",
86
- color: colors.zinc[700],
87
- },
88
- headerCount: {
89
- backgroundColor: colors.zinc[200],
90
- paddingHorizontal: 8,
91
- paddingVertical: 2,
92
- borderRadius: 10,
93
- },
94
- headerCountText: {
95
- fontSize: 12,
96
- color: colors.zinc[500],
97
- },
98
- addCard: {
99
- flexDirection: "row",
100
- alignItems: "center",
101
- justifyContent: "center",
102
- gap: 6,
103
- paddingVertical: 8,
104
- borderRadius: 6,
105
- borderWidth: 1,
106
- borderColor: colors.zinc[200],
107
- borderStyle: "dashed",
108
- backgroundColor: colors.white,
109
- },
110
- addCardText: {
111
- fontSize: 13,
112
- color: colors.zinc[400],
113
- },
114
- collapsedColumn: {
115
- backgroundColor: colors.zinc[50],
116
- borderRadius: 8,
117
- borderWidth: 1,
118
- borderColor: colors.zinc[200],
119
- alignItems: "center",
120
- flex: 1,
121
- paddingVertical: 12,
122
- paddingHorizontal: 4,
123
- alignSelf: "stretch",
124
- },
125
- collapsedContent: {
126
- flex: 1,
127
- alignItems: "center",
128
- gap: 8,
129
- },
130
- collapsedCount: {
131
- backgroundColor: colors.zinc[200],
132
- paddingHorizontal: 8,
133
- paddingVertical: 2,
134
- borderRadius: 10,
135
- },
136
- collapsedCountText: {
137
- fontSize: 12,
138
- fontWeight: "500",
139
- color: colors.zinc[600],
140
- },
141
- collapsedTitle: {
142
- writingDirection: "ltr",
143
- transform: [{ rotate: "180deg" }],
144
- // @ts-expect-error web-only property for vertical text
145
- writingMode: "vertical-rl",
146
- fontSize: 14,
147
- fontWeight: "600",
148
- color: colors.zinc[700],
149
- textAlign: "center",
150
- },
151
- collapsedDropIndicator: {
152
- position: "absolute",
153
- top: 8,
154
- left: 4,
155
- right: 4,
156
- height: 3,
157
- backgroundColor: colors.blue[500],
158
- borderRadius: 2,
159
- },
160
- });
@@ -1,157 +0,0 @@
1
- import React, { memo, useEffect, useState } from "react";
2
- import { StyleSheet, View } from "react-native";
3
- import { colors } from "../colors";
4
- import {
5
- DragInfo,
6
- DragPreviewInfo,
7
- KanbanColumn,
8
- KanbanRenderCardProps,
9
- KanbanRenderColumnHeaderProps,
10
- } from "./types";
11
-
12
- interface DragPreviewProps<T> {
13
- columns: KanbanColumn<T>[];
14
- dragInfo: DragInfo | null;
15
- dragPreview: DragPreviewInfo | null;
16
- renderCard: (props: KanbanRenderCardProps<T>) => React.ReactNode;
17
- renderColumnHeader: (props: KanbanRenderColumnHeaderProps) => React.ReactNode;
18
- onPositionUpdate: (callback: (x: number, y: number) => void) => void;
19
- initialPosition: { x: number; y: number } | null;
20
- }
21
-
22
- function DragPreviewInner<T>({
23
- columns,
24
- dragInfo,
25
- dragPreview,
26
- renderCard,
27
- renderColumnHeader,
28
- onPositionUpdate,
29
- initialPosition,
30
- }: DragPreviewProps<T>) {
31
- const [position, setPosition] = useState(initialPosition);
32
-
33
- // Register position update callback
34
- useEffect(() => {
35
- onPositionUpdate((x, y) => setPosition({ x, y }));
36
- return () => onPositionUpdate(() => {});
37
- }, [onPositionUpdate]);
38
-
39
- // Sync with initial position
40
- useEffect(() => {
41
- if (initialPosition) setPosition(initialPosition);
42
- }, [initialPosition]);
43
-
44
- // Clear position when drag ends
45
- useEffect(() => {
46
- if (!dragInfo) setPosition(null);
47
- }, [dragInfo]);
48
-
49
- if (!dragInfo || !position || !dragPreview) return null;
50
-
51
- const left = position.x - dragPreview.offsetX;
52
- const top = position.y - dragPreview.offsetY;
53
-
54
- if (dragInfo.type === "card") {
55
- const column = columns.find((col) => col.key === dragInfo.sourceColumnKey);
56
- const item = column?.items.find((i) => i.id === dragInfo.id);
57
- if (!item) return null;
58
-
59
- return (
60
- <div style={{ ...previewStyle, left, top, width: dragPreview.width }}>
61
- {renderCard({
62
- item: item.data,
63
- columnKey: dragInfo.sourceColumnKey,
64
- id: dragInfo.id,
65
- hovered: false,
66
- isDragging: true,
67
- })}
68
- </div>
69
- );
70
- }
71
-
72
- if (dragInfo.type === "column") {
73
- const column = columns.find((col) => col.key === dragInfo.id);
74
- if (!column) return null;
75
-
76
- return (
77
- <div
78
- style={{
79
- ...previewStyle,
80
- left,
81
- top,
82
- width: dragPreview.width,
83
- height: dragPreview.height,
84
- }}
85
- >
86
- <View style={styles.columnPreview}>
87
- <View style={styles.columnPreviewHeader}>
88
- {renderColumnHeader({
89
- columnKey: column.key,
90
- title: column.title,
91
- itemCount: column.items.length,
92
- isMinimized: false,
93
- onMinimizeToggle: () => {},
94
- })}
95
- </View>
96
- <View style={styles.columnPreviewContent}>
97
- {column.items.slice(0, 3).map((item) => (
98
- <View key={item.id} style={styles.columnPreviewCard}>
99
- {renderCard({
100
- item: item.data,
101
- columnKey: column.key,
102
- id: item.id,
103
- hovered: false,
104
- isDragging: true,
105
- })}
106
- </View>
107
- ))}
108
- {column.items.length > 3 && (
109
- <View style={styles.columnPreviewMore}>
110
- <span style={{ color: colors.zinc[500], fontSize: 12 }}>
111
- +{column.items.length - 3} more
112
- </span>
113
- </View>
114
- )}
115
- </View>
116
- </View>
117
- </div>
118
- );
119
- }
120
-
121
- return null;
122
- }
123
-
124
- export const DragPreview = memo(DragPreviewInner) as typeof DragPreviewInner;
125
-
126
- const previewStyle: React.CSSProperties = {
127
- position: "fixed",
128
- zIndex: 9999,
129
- pointerEvents: "none",
130
- opacity: 0.85,
131
- transform: "rotate(2deg)",
132
- boxShadow: "0 8px 24px rgba(0,0,0,0.15)",
133
- };
134
-
135
- const styles = StyleSheet.create({
136
- columnPreview: {
137
- backgroundColor: colors.zinc[50],
138
- borderRadius: 8,
139
- borderWidth: 1,
140
- borderColor: colors.zinc[200],
141
- overflow: "hidden",
142
- },
143
- columnPreviewHeader: {
144
- borderBottomWidth: 1,
145
- borderBottomColor: colors.zinc[200],
146
- },
147
- columnPreviewContent: {
148
- padding: 4,
149
- },
150
- columnPreviewCard: {
151
- marginVertical: 2,
152
- },
153
- columnPreviewMore: {
154
- padding: 8,
155
- alignItems: "center",
156
- },
157
- });
@@ -1,13 +0,0 @@
1
- export { KanbanBoard } from "./kanban_board";
2
- export type { KanbanBoardProps } from "./kanban_board";
3
- export type {
4
- KanbanColumn,
5
- KanbanItem,
6
- KanbanRenderCardProps,
7
- KanbanRenderColumnHeaderProps,
8
- KanbanRenderCollapsedColumnProps,
9
- KanbanRenderAddCardPlaceholderProps,
10
- KanbanRenderInsertCardButtonProps,
11
- CardMoveResult,
12
- ColumnMoveResult,
13
- } from "./types";
@@ -1,135 +0,0 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
2
- import { KanbanRenderInsertCardButtonProps } from "./types";
3
- import { Pressable, View, StyleSheet } from "react-native";
4
- import { colors } from "../colors";
5
- import { Icon } from "../icon";
6
-
7
- const HOVER_DELAY_MS = 200;
8
-
9
- // Interactive zone that shows an insert button on hover
10
- export function InsertCardZone({
11
- height,
12
- columnKey,
13
- index,
14
- onAddCardAtIndex,
15
- renderInsertCardButton,
16
- isDragInProgress,
17
- }: {
18
- height: number;
19
- columnKey: string;
20
- index: number;
21
- onAddCardAtIndex: (columnKey: string, index: number) => void;
22
- renderInsertCardButton?: (props: KanbanRenderInsertCardButtonProps) => React.ReactNode;
23
- /** When true, drag is in progress and insert UI should be hidden */
24
- isDragInProgress?: boolean;
25
- }) {
26
- const [isHovered, setIsHovered] = useState(false);
27
- const hoverTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
28
-
29
- // Cleanup timeout on unmount to prevent memory leak
30
- useEffect(() => {
31
- return () => {
32
- if (hoverTimeoutRef.current) {
33
- clearTimeout(hoverTimeoutRef.current);
34
- hoverTimeoutRef.current = null;
35
- }
36
- };
37
- }, []);
38
-
39
- const handlePointerEnter = useCallback(() => {
40
- hoverTimeoutRef.current = setTimeout(() => {
41
- setIsHovered(true);
42
- }, HOVER_DELAY_MS);
43
- }, []);
44
-
45
- const handlePointerLeave = useCallback(() => {
46
- if (hoverTimeoutRef.current) {
47
- clearTimeout(hoverTimeoutRef.current);
48
- hoverTimeoutRef.current = null;
49
- }
50
- setIsHovered(false);
51
- }, []);
52
-
53
- const handlePress = useCallback(() => {
54
- onAddCardAtIndex(columnKey, index);
55
- }, [columnKey, index, onAddCardAtIndex]);
56
-
57
- // Don't show insert UI during drag operations
58
- const showInsertUI = isHovered && !isDragInProgress;
59
-
60
- if (renderInsertCardButton) {
61
- return (
62
- <View
63
- style={[insertZoneStyles.container, { height }]}
64
- onPointerEnter={handlePointerEnter}
65
- onPointerLeave={handlePointerLeave}
66
- >
67
- {showInsertUI &&
68
- renderInsertCardButton({
69
- columnKey,
70
- index,
71
- onPress: handlePress,
72
- })}
73
- </View>
74
- );
75
- }
76
-
77
- return (
78
- <Pressable
79
- onPress={handlePress}
80
- onPointerEnter={handlePointerEnter}
81
- onPointerLeave={handlePointerLeave}
82
- style={[insertZoneStyles.container, { height }]}
83
- >
84
- {showInsertUI && (
85
- <View style={insertZoneStyles.buttonWrapper}>
86
- <View style={insertZoneStyles.buttonContent}>
87
- <View style={insertZoneStyles.line} />
88
- <View style={insertZoneStyles.plusCircle}>
89
- <Icon name="plus" size={16} color={colors.white} />
90
- </View>
91
- <View style={insertZoneStyles.line} />
92
- </View>
93
- </View>
94
- )}
95
- </Pressable>
96
- );
97
- }
98
-
99
- const insertZoneStyles = StyleSheet.create({
100
- container: {
101
- justifyContent: "center",
102
- alignItems: "center",
103
- overflow: "visible",
104
- },
105
- buttonWrapper: {
106
- position: "absolute",
107
- left: 0,
108
- right: 0,
109
- top: -3,
110
- height: 14,
111
- zIndex: 10,
112
- },
113
- buttonContent: {
114
- flexDirection: "row",
115
- alignItems: "center",
116
- width: "100%",
117
- height: "100%",
118
- paddingHorizontal: 4,
119
- },
120
- line: {
121
- flex: 1,
122
- height: 2,
123
- backgroundColor: colors.zinc[400],
124
- borderRadius: 1,
125
- },
126
- plusCircle: {
127
- width: 18,
128
- height: 18,
129
- borderRadius: 6,
130
- backgroundColor: colors.zinc[900],
131
- justifyContent: "center",
132
- alignItems: "center",
133
- marginHorizontal: 4,
134
- },
135
- });