@pol-studios/features 1.0.0 → 1.0.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/README.md +329 -0
- package/dist/comments/index.js +1 -1
- package/dist/index.js +2 -2
- package/dist/punch-list/index.js +1 -1
- package/package.json +39 -20
package/README.md
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# @pol-studios/features
|
|
2
|
+
|
|
3
|
+
> Feature modules for POL applications
|
|
4
|
+
|
|
5
|
+
Business feature modules that encapsulate common application patterns including comments, ordering, punch-lists, and filter utilities. Built on top of `@pol-studios/db` and `@pol-studios/auth`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @pol-studios/features
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Peer Dependencies
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add react @pol-studios/db @pol-studios/auth @pol-studios/utils
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { CommentProvider, useComments } from "@pol-studios/features/comments";
|
|
23
|
+
import { useOrderManager } from "@pol-studios/features/ordering";
|
|
24
|
+
import { usePunchListPage } from "@pol-studios/features/punch-list";
|
|
25
|
+
import { FilterProvider, useFilterContext } from "@pol-studios/features/filter-utils";
|
|
26
|
+
|
|
27
|
+
// Comments on any entity
|
|
28
|
+
function ProjectComments({ projectId }) {
|
|
29
|
+
return (
|
|
30
|
+
<CommentProvider entityType="project" entityId={projectId}>
|
|
31
|
+
<CommentList />
|
|
32
|
+
</CommentProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Drag-and-drop ordering
|
|
37
|
+
function TaskList({ tasks }) {
|
|
38
|
+
const { items, moveItem, saveOrder } = useOrderManager(tasks);
|
|
39
|
+
// ... render sortable list
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Subpath Exports
|
|
44
|
+
|
|
45
|
+
| Path | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `@pol-studios/features` | All exports combined |
|
|
48
|
+
| `@pol-studios/features/comments` | Comment system for entities |
|
|
49
|
+
| `@pol-studios/features/ordering` | Drag-and-drop ordering utilities |
|
|
50
|
+
| `@pol-studios/features/punch-list` | Punch-list / checklist functionality |
|
|
51
|
+
| `@pol-studios/features/filter-utils` | Filter builder utilities |
|
|
52
|
+
|
|
53
|
+
## API Reference
|
|
54
|
+
|
|
55
|
+
### Comments Module
|
|
56
|
+
|
|
57
|
+
Provides a complete comment system with reactions, read tracking, and quotes.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import {
|
|
61
|
+
CommentProvider,
|
|
62
|
+
useComments,
|
|
63
|
+
CommentContext,
|
|
64
|
+
} from "@pol-studios/features/comments";
|
|
65
|
+
|
|
66
|
+
import type {
|
|
67
|
+
CommentContextType,
|
|
68
|
+
CommentWithRelations,
|
|
69
|
+
Quote,
|
|
70
|
+
ProfileRow,
|
|
71
|
+
CoreCommentRow,
|
|
72
|
+
CoreCommentReactionRow,
|
|
73
|
+
CoreCommentReadRow,
|
|
74
|
+
CommentProviderProps,
|
|
75
|
+
} from "@pol-studios/features/comments";
|
|
76
|
+
|
|
77
|
+
// Wrap component with CommentProvider
|
|
78
|
+
<CommentProvider
|
|
79
|
+
entityType="project"
|
|
80
|
+
entityId={projectId}
|
|
81
|
+
userId={currentUserId}
|
|
82
|
+
>
|
|
83
|
+
<CommentSection />
|
|
84
|
+
</CommentProvider>
|
|
85
|
+
|
|
86
|
+
// Use comments in child components
|
|
87
|
+
function CommentSection() {
|
|
88
|
+
const {
|
|
89
|
+
comments, // All comments with relations
|
|
90
|
+
isLoading, // Loading state
|
|
91
|
+
addComment, // Add new comment
|
|
92
|
+
updateComment, // Edit comment
|
|
93
|
+
deleteComment, // Remove comment
|
|
94
|
+
addReaction, // React to comment
|
|
95
|
+
removeReaction, // Remove reaction
|
|
96
|
+
markAsRead, // Mark comment as read
|
|
97
|
+
replyTo, // Reply to specific comment
|
|
98
|
+
quote, // Quote text from comment
|
|
99
|
+
unreadCount, // Number of unread comments
|
|
100
|
+
} = useComments();
|
|
101
|
+
|
|
102
|
+
const handleSubmit = (text: string) => {
|
|
103
|
+
addComment({ text, parentId: null });
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<div>Unread: {unreadCount}</div>
|
|
109
|
+
{comments.map(comment => (
|
|
110
|
+
<Comment
|
|
111
|
+
key={comment.id}
|
|
112
|
+
comment={comment}
|
|
113
|
+
onReply={() => replyTo(comment.id)}
|
|
114
|
+
onReact={(emoji) => addReaction(comment.id, emoji)}
|
|
115
|
+
/>
|
|
116
|
+
))}
|
|
117
|
+
<CommentInput onSubmit={handleSubmit} />
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Ordering Module
|
|
124
|
+
|
|
125
|
+
Utilities for managing item order with optimistic updates.
|
|
126
|
+
|
|
127
|
+
```tsx
|
|
128
|
+
import {
|
|
129
|
+
useOrderHint,
|
|
130
|
+
useOrderManager,
|
|
131
|
+
} from "@pol-studios/features/ordering";
|
|
132
|
+
|
|
133
|
+
// Generate order hints for positioning
|
|
134
|
+
function useOrderHintExample() {
|
|
135
|
+
const { generateHint, getHintBetween } = useOrderHint();
|
|
136
|
+
|
|
137
|
+
// Get hint for inserting at position
|
|
138
|
+
const hint = generateHint(index, items);
|
|
139
|
+
|
|
140
|
+
// Get hint between two items
|
|
141
|
+
const betweenHint = getHintBetween(itemA.order_hint, itemB.order_hint);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Full order management
|
|
145
|
+
function SortableList({ initialItems }) {
|
|
146
|
+
const {
|
|
147
|
+
items, // Ordered items
|
|
148
|
+
moveItem, // Move item to new position
|
|
149
|
+
reorder, // Reorder by drag-and-drop result
|
|
150
|
+
saveOrder, // Persist order to database
|
|
151
|
+
isDirty, // Has unsaved changes
|
|
152
|
+
isLoading, // Saving in progress
|
|
153
|
+
} = useOrderManager(initialItems, {
|
|
154
|
+
table: "tasks",
|
|
155
|
+
orderColumn: "order_hint",
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const handleDragEnd = (result) => {
|
|
159
|
+
if (!result.destination) return;
|
|
160
|
+
reorder(result.source.index, result.destination.index);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return (
|
|
164
|
+
<DragDropContext onDragEnd={handleDragEnd}>
|
|
165
|
+
<Droppable droppableId="list">
|
|
166
|
+
{(provided) => (
|
|
167
|
+
<div ref={provided.innerRef} {...provided.droppableProps}>
|
|
168
|
+
{items.map((item, index) => (
|
|
169
|
+
<Draggable key={item.id} draggableId={item.id} index={index}>
|
|
170
|
+
{(provided) => <Item item={item} provided={provided} />}
|
|
171
|
+
</Draggable>
|
|
172
|
+
))}
|
|
173
|
+
{provided.placeholder}
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</Droppable>
|
|
177
|
+
<button onClick={saveOrder} disabled={!isDirty || isLoading}>
|
|
178
|
+
Save Order
|
|
179
|
+
</button>
|
|
180
|
+
</DragDropContext>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Punch-List Module
|
|
186
|
+
|
|
187
|
+
Punch-list / checklist page functionality for tracking items.
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
import { usePunchListPage } from "@pol-studios/features/punch-list";
|
|
191
|
+
|
|
192
|
+
function PunchListPage({ projectId }) {
|
|
193
|
+
const {
|
|
194
|
+
items, // Punch-list items
|
|
195
|
+
isLoading, // Loading state
|
|
196
|
+
filter, // Current filter
|
|
197
|
+
setFilter, // Update filter
|
|
198
|
+
stats, // Completion statistics
|
|
199
|
+
addItem, // Add new item
|
|
200
|
+
updateItem, // Update item
|
|
201
|
+
deleteItem, // Remove item
|
|
202
|
+
toggleComplete, // Toggle item completion
|
|
203
|
+
assignTo, // Assign item to user
|
|
204
|
+
groupBy, // Grouped items (by status, assignee, etc.)
|
|
205
|
+
} = usePunchListPage({
|
|
206
|
+
projectId,
|
|
207
|
+
defaultFilter: { status: "open" },
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<div>
|
|
212
|
+
<Stats total={stats.total} completed={stats.completed} />
|
|
213
|
+
<FilterBar filter={filter} onChange={setFilter} />
|
|
214
|
+
<ItemList
|
|
215
|
+
items={items}
|
|
216
|
+
onToggle={toggleComplete}
|
|
217
|
+
onEdit={updateItem}
|
|
218
|
+
onDelete={deleteItem}
|
|
219
|
+
/>
|
|
220
|
+
<AddItemForm onAdd={addItem} />
|
|
221
|
+
</div>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Filter Utils Module
|
|
227
|
+
|
|
228
|
+
Utilities for building dynamic filter UIs.
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import {
|
|
232
|
+
FilterProvider,
|
|
233
|
+
useFilterContext,
|
|
234
|
+
useNestedFilterOptions,
|
|
235
|
+
hookOnChange,
|
|
236
|
+
getDefaultValue,
|
|
237
|
+
getComparisonOptions,
|
|
238
|
+
getDefaultCondition,
|
|
239
|
+
genId,
|
|
240
|
+
getPropertyKey,
|
|
241
|
+
recurseToProperty,
|
|
242
|
+
getProperty,
|
|
243
|
+
} from "@pol-studios/features/filter-utils";
|
|
244
|
+
|
|
245
|
+
// Wrap filter UI with provider
|
|
246
|
+
function FilterableList({ data, fields }) {
|
|
247
|
+
return (
|
|
248
|
+
<FilterProvider fields={fields}>
|
|
249
|
+
<FilterBar />
|
|
250
|
+
<DataList data={data} />
|
|
251
|
+
</FilterProvider>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Build filter UI
|
|
256
|
+
function FilterBar() {
|
|
257
|
+
const {
|
|
258
|
+
filters, // Current filter conditions
|
|
259
|
+
addFilter, // Add new filter condition
|
|
260
|
+
removeFilter, // Remove filter condition
|
|
261
|
+
updateFilter, // Update filter value
|
|
262
|
+
clearFilters, // Clear all filters
|
|
263
|
+
applyFilters, // Apply filters to data
|
|
264
|
+
fields, // Available fields to filter
|
|
265
|
+
} = useFilterContext();
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
{filters.map((filter) => (
|
|
270
|
+
<FilterRow
|
|
271
|
+
key={filter.id}
|
|
272
|
+
filter={filter}
|
|
273
|
+
fields={fields}
|
|
274
|
+
onUpdate={(updates) => updateFilter(filter.id, updates)}
|
|
275
|
+
onRemove={() => removeFilter(filter.id)}
|
|
276
|
+
/>
|
|
277
|
+
))}
|
|
278
|
+
<button onClick={addFilter}>Add Filter</button>
|
|
279
|
+
<button onClick={clearFilters}>Clear</button>
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Nested filter options (for hierarchical data)
|
|
285
|
+
function CategoryFilter({ categories }) {
|
|
286
|
+
const options = useNestedFilterOptions(categories, {
|
|
287
|
+
labelKey: "name",
|
|
288
|
+
valueKey: "id",
|
|
289
|
+
childrenKey: "subcategories",
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return <Select options={options} />;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Utility functions
|
|
296
|
+
const comparisonOptions = getComparisonOptions("string"); // ["equals", "contains", "startsWith", ...]
|
|
297
|
+
const defaultValue = getDefaultValue("number"); // 0
|
|
298
|
+
const defaultCondition = getDefaultCondition("date"); // { operator: "equals", value: null }
|
|
299
|
+
const id = genId(); // Unique filter ID
|
|
300
|
+
const key = getPropertyKey(field); // Normalized property key
|
|
301
|
+
const value = getProperty(obj, "nested.path"); // Get nested value
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## TypeScript Types
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
import type {
|
|
308
|
+
// Comments types
|
|
309
|
+
CommentContextType,
|
|
310
|
+
CommentWithRelations,
|
|
311
|
+
Quote,
|
|
312
|
+
ProfileRow,
|
|
313
|
+
CoreCommentRow,
|
|
314
|
+
CoreCommentReactionRow,
|
|
315
|
+
CoreCommentReadRow,
|
|
316
|
+
CommentProviderProps,
|
|
317
|
+
} from "@pol-studios/features/comments";
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Related Packages
|
|
321
|
+
|
|
322
|
+
- [@pol-studios/db](../db) - Database layer (required peer dependency)
|
|
323
|
+
- [@pol-studios/auth](../auth) - Authentication (required peer dependency)
|
|
324
|
+
- [@pol-studios/utils](../utils) - Utility functions (required peer dependency)
|
|
325
|
+
- [@pol-studios/ui](../ui) - UI components for rendering features
|
|
326
|
+
|
|
327
|
+
## License
|
|
328
|
+
|
|
329
|
+
UNLICENSED
|
package/dist/comments/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useDbUpsert,
|
|
15
15
|
useSupabase
|
|
16
16
|
} from "@pol-studios/db";
|
|
17
|
-
import { useAuth } from "@pol-studios/auth";
|
|
17
|
+
import { useAuth } from "@pol-studios/db/auth";
|
|
18
18
|
import { isUsable } from "@pol-studios/utils";
|
|
19
19
|
import { Comment } from "@pol-studios/db";
|
|
20
20
|
import { jsx } from "react/jsx-runtime";
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useDbUpsert,
|
|
15
15
|
useSupabase
|
|
16
16
|
} from "@pol-studios/db";
|
|
17
|
-
import { useAuth } from "@pol-studios/auth";
|
|
17
|
+
import { useAuth } from "@pol-studios/db/auth";
|
|
18
18
|
import { isUsable } from "@pol-studios/utils";
|
|
19
19
|
import { Comment } from "@pol-studios/db";
|
|
20
20
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -450,7 +450,7 @@ import {
|
|
|
450
450
|
} from "@pol-studios/db";
|
|
451
451
|
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
452
452
|
import { useDbUpsert as useDbUpsert3 } from "@pol-studios/db";
|
|
453
|
-
import { useAuth as useAuth2 } from "@pol-studios/auth";
|
|
453
|
+
import { useAuth as useAuth2 } from "@pol-studios/db/auth";
|
|
454
454
|
function usePunchListPage(relation, entityId) {
|
|
455
455
|
const supabase = useSupabase3();
|
|
456
456
|
const upsert = useDbUpsert3("PunchListPage", ["id"]);
|
package/dist/punch-list/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "@pol-studios/db";
|
|
5
5
|
import { useEffect, useState } from "react";
|
|
6
6
|
import { useDbUpsert } from "@pol-studios/db";
|
|
7
|
-
import { useAuth } from "@pol-studios/auth";
|
|
7
|
+
import { useAuth } from "@pol-studios/db/auth";
|
|
8
8
|
function usePunchListPage(relation, entityId) {
|
|
9
9
|
const supabase = useSupabase();
|
|
10
10
|
const upsert = useDbUpsert("PunchListPage", ["id"]);
|
package/package.json
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pol-studios/features",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Feature modules for POL applications",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
|
-
"files": [
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
10
12
|
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"./
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./comments": {
|
|
18
|
+
"import": "./dist/comments/index.js",
|
|
19
|
+
"types": "./dist/comments/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./ordering": {
|
|
22
|
+
"import": "./dist/ordering/index.js",
|
|
23
|
+
"types": "./dist/ordering/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./punch-list": {
|
|
26
|
+
"import": "./dist/punch-list/index.js",
|
|
27
|
+
"types": "./dist/punch-list/index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./filter-utils": {
|
|
30
|
+
"import": "./dist/filter-utils/index.js",
|
|
31
|
+
"types": "./dist/filter-utils/index.d.ts"
|
|
32
|
+
}
|
|
23
33
|
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"features",
|
|
36
|
+
"comments",
|
|
37
|
+
"ordering",
|
|
38
|
+
"punch-list"
|
|
39
|
+
],
|
|
24
40
|
"publishConfig": {
|
|
25
41
|
"access": "public"
|
|
26
42
|
},
|
|
@@ -29,15 +45,18 @@
|
|
|
29
45
|
},
|
|
30
46
|
"peerDependencies": {
|
|
31
47
|
"@pol-studios/db": ">=1.0.0",
|
|
32
|
-
"@pol-studios/auth": ">=1.0.0",
|
|
33
48
|
"@pol-studios/utils": ">=1.0.0",
|
|
34
49
|
"react": "^18.0.0 || ^19.0.0"
|
|
35
50
|
},
|
|
36
51
|
"devDependencies": {
|
|
37
|
-
"@pol-studios/db": "workspace:*",
|
|
38
|
-
"@pol-studios/auth": "workspace:*",
|
|
39
|
-
"@pol-studios/utils": "workspace:*",
|
|
40
52
|
"tsup": "^8.0.0",
|
|
41
|
-
"typescript": "^5.0.0"
|
|
53
|
+
"typescript": "^5.0.0",
|
|
54
|
+
"@pol-studios/db": "1.0.1",
|
|
55
|
+
"@pol-studios/utils": "1.0.1"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"dev": "tsup --watch",
|
|
60
|
+
"typecheck": "tsc --noEmit"
|
|
42
61
|
}
|
|
43
|
-
}
|
|
62
|
+
}
|