@lotics/ui 2.6.0 → 3.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.
Files changed (47) hide show
  1. package/package.json +2 -15
  2. package/src/format_date.test.ts +64 -0
  3. package/src/format_date.ts +71 -0
  4. package/src/react_native.d.ts +2 -2
  5. package/src/cell_date.tsx +0 -30
  6. package/src/cell_date_format.test.ts +0 -32
  7. package/src/cell_date_format.ts +0 -73
  8. package/src/cell_number.test.ts +0 -42
  9. package/src/cell_number.tsx +0 -25
  10. package/src/cell_number_format.ts +0 -42
  11. package/src/cell_select.tsx +0 -68
  12. package/src/cell_text.tsx +0 -45
  13. package/src/grid/data_grid.tsx +0 -2003
  14. package/src/grid/data_grid_columns.test.ts +0 -72
  15. package/src/grid/data_grid_columns.ts +0 -30
  16. package/src/grid/data_grid_context.ts +0 -119
  17. package/src/grid/dispatch_safely.ts +0 -39
  18. package/src/grid/engine.module.css +0 -114
  19. package/src/grid/engine.tsx +0 -1042
  20. package/src/grid/helpers.ts +0 -205
  21. package/src/grid/layout.test.ts +0 -515
  22. package/src/grid/layout.ts +0 -425
  23. package/src/grid/recycling.test.ts +0 -236
  24. package/src/grid/recycling.ts +0 -172
  25. package/src/grid/row_cell.module.css +0 -105
  26. package/src/grid/row_cell.tsx +0 -313
  27. package/src/grid/search_highlight.ts +0 -71
  28. package/src/grid/select_cell.tsx +0 -58
  29. package/src/grid/select_group_summary_cell.tsx +0 -76
  30. package/src/grid/select_header_cell.tsx +0 -32
  31. package/src/grid/skeleton_row.module.css +0 -34
  32. package/src/grid/skeleton_row.tsx +0 -20
  33. package/src/grid/use_grid_groups.ts +0 -311
  34. package/src/grid/use_scroll_to_cell.ts +0 -135
  35. package/src/grid/use_virtual_grid.ts +0 -383
  36. package/src/grid/visibility.test.ts +0 -208
  37. package/src/grid/visibility.ts +0 -77
  38. package/src/kanban/constants.ts +0 -18
  39. package/src/kanban/default_renderers.tsx +0 -160
  40. package/src/kanban/drag_preview.tsx +0 -157
  41. package/src/kanban/index.ts +0 -13
  42. package/src/kanban/insert_card_zone.tsx +0 -135
  43. package/src/kanban/kanban_board.tsx +0 -635
  44. package/src/kanban/kanban_card.tsx +0 -321
  45. package/src/kanban/kanban_column.tsx +0 -499
  46. package/src/kanban/placeholders.tsx +0 -54
  47. 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
- });