@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 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
@@ -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"]);
@@ -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.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": ["dist"],
9
+ "files": [
10
+ "dist"
11
+ ],
10
12
  "exports": {
11
- ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" },
12
- "./comments": { "import": "./dist/comments/index.js", "types": "./dist/comments/index.d.ts" },
13
- "./ordering": { "import": "./dist/ordering/index.js", "types": "./dist/ordering/index.d.ts" },
14
- "./punch-list": { "import": "./dist/punch-list/index.js", "types": "./dist/punch-list/index.d.ts" },
15
- "./filter-utils": { "import": "./dist/filter-utils/index.js", "types": "./dist/filter-utils/index.d.ts" }
16
- },
17
- "keywords": ["features", "comments", "ordering", "punch-list"],
18
- "scripts": {
19
- "build": "tsup",
20
- "dev": "tsup --watch",
21
- "typecheck": "tsc --noEmit",
22
- "prepublishOnly": "pnpm build"
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
+ }