@pol-studios/features
v1.0.9
Published
Feature modules for POL applications
Maintainers
Readme
@pol-studios/features
Feature modules for POL applications
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/db/auth.
Installation
pnpm add @pol-studios/featuresPeer Dependencies
pnpm add react @pol-studios/db @pol-studios/db/auth @pol-studios/utilsQuick Start
import { CommentProvider, useComments } from "@pol-studios/features/comments";
import { useOrderManager } from "@pol-studios/features/ordering";
import { usePunchListPage } from "@pol-studios/features/punch-list";
import { FilterProvider, useFilterContext } from "@pol-studios/features/filter-utils";
// Comments on any entity
function ProjectComments({ projectId }) {
return (
<CommentProvider entityType="project" entityId={projectId}>
<CommentList />
</CommentProvider>
);
}
// Drag-and-drop ordering
function TaskList({ tasks }) {
const { items, moveItem, saveOrder } = useOrderManager(tasks);
// ... render sortable list
}Subpath Exports
| Path | Description |
|------|-------------|
| @pol-studios/features | All exports combined |
| @pol-studios/features/comments | Comment system for entities |
| @pol-studios/features/ordering | Drag-and-drop ordering utilities |
| @pol-studios/features/punch-list | Punch-list / checklist functionality |
| @pol-studios/features/filter-utils | Filter builder utilities |
API Reference
Comments Module
Provides a complete comment system with reactions, read tracking, and quotes.
import {
CommentProvider,
useComments,
CommentContext,
} from "@pol-studios/features/comments";
import type {
CommentContextType,
CommentWithRelations,
Quote,
ProfileRow,
CoreCommentRow,
CoreCommentReactionRow,
CoreCommentReadRow,
CommentProviderProps,
} from "@pol-studios/features/comments";
// Wrap component with CommentProvider
<CommentProvider
entityType="project"
entityId={projectId}
userId={currentUserId}
>
<CommentSection />
</CommentProvider>
// Use comments in child components
function CommentSection() {
const {
comments, // All comments with relations
isLoading, // Loading state
addComment, // Add new comment
updateComment, // Edit comment
deleteComment, // Remove comment
addReaction, // React to comment
removeReaction, // Remove reaction
markAsRead, // Mark comment as read
replyTo, // Reply to specific comment
quote, // Quote text from comment
unreadCount, // Number of unread comments
} = useComments();
const handleSubmit = (text: string) => {
addComment({ text, parentId: null });
};
return (
<div>
<div>Unread: {unreadCount}</div>
{comments.map(comment => (
<Comment
key={comment.id}
comment={comment}
onReply={() => replyTo(comment.id)}
onReact={(emoji) => addReaction(comment.id, emoji)}
/>
))}
<CommentInput onSubmit={handleSubmit} />
</div>
);
}Ordering Module
Utilities for managing item order with optimistic updates.
import {
useOrderHint,
useOrderManager,
} from "@pol-studios/features/ordering";
// Generate order hints for positioning
function useOrderHintExample() {
const { generateHint, getHintBetween } = useOrderHint();
// Get hint for inserting at position
const hint = generateHint(index, items);
// Get hint between two items
const betweenHint = getHintBetween(itemA.order_hint, itemB.order_hint);
}
// Full order management
function SortableList({ initialItems }) {
const {
items, // Ordered items
moveItem, // Move item to new position
reorder, // Reorder by drag-and-drop result
saveOrder, // Persist order to database
isDirty, // Has unsaved changes
isLoading, // Saving in progress
} = useOrderManager(initialItems, {
table: "tasks",
orderColumn: "order_hint",
});
const handleDragEnd = (result) => {
if (!result.destination) return;
reorder(result.source.index, result.destination.index);
};
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="list">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => <Item item={item} provided={provided} />}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
<button onClick={saveOrder} disabled={!isDirty || isLoading}>
Save Order
</button>
</DragDropContext>
);
}Punch-List Module
Punch-list / checklist page functionality for tracking items.
import { usePunchListPage } from "@pol-studios/features/punch-list";
function PunchListPage({ projectId }) {
const {
items, // Punch-list items
isLoading, // Loading state
filter, // Current filter
setFilter, // Update filter
stats, // Completion statistics
addItem, // Add new item
updateItem, // Update item
deleteItem, // Remove item
toggleComplete, // Toggle item completion
assignTo, // Assign item to user
groupBy, // Grouped items (by status, assignee, etc.)
} = usePunchListPage({
projectId,
defaultFilter: { status: "open" },
});
return (
<div>
<Stats total={stats.total} completed={stats.completed} />
<FilterBar filter={filter} onChange={setFilter} />
<ItemList
items={items}
onToggle={toggleComplete}
onEdit={updateItem}
onDelete={deleteItem}
/>
<AddItemForm onAdd={addItem} />
</div>
);
}Filter Utils Module
Utilities for building dynamic filter UIs.
import {
FilterProvider,
useFilterContext,
useNestedFilterOptions,
hookOnChange,
getDefaultValue,
getComparisonOptions,
getDefaultCondition,
genId,
getPropertyKey,
recurseToProperty,
getProperty,
} from "@pol-studios/features/filter-utils";
// Wrap filter UI with provider
function FilterableList({ data, fields }) {
return (
<FilterProvider fields={fields}>
<FilterBar />
<DataList data={data} />
</FilterProvider>
);
}
// Build filter UI
function FilterBar() {
const {
filters, // Current filter conditions
addFilter, // Add new filter condition
removeFilter, // Remove filter condition
updateFilter, // Update filter value
clearFilters, // Clear all filters
applyFilters, // Apply filters to data
fields, // Available fields to filter
} = useFilterContext();
return (
<div>
{filters.map((filter) => (
<FilterRow
key={filter.id}
filter={filter}
fields={fields}
onUpdate={(updates) => updateFilter(filter.id, updates)}
onRemove={() => removeFilter(filter.id)}
/>
))}
<button onClick={addFilter}>Add Filter</button>
<button onClick={clearFilters}>Clear</button>
</div>
);
}
// Nested filter options (for hierarchical data)
function CategoryFilter({ categories }) {
const options = useNestedFilterOptions(categories, {
labelKey: "name",
valueKey: "id",
childrenKey: "subcategories",
});
return <Select options={options} />;
}
// Utility functions
const comparisonOptions = getComparisonOptions("string"); // ["equals", "contains", "startsWith", ...]
const defaultValue = getDefaultValue("number"); // 0
const defaultCondition = getDefaultCondition("date"); // { operator: "equals", value: null }
const id = genId(); // Unique filter ID
const key = getPropertyKey(field); // Normalized property key
const value = getProperty(obj, "nested.path"); // Get nested valueTypeScript Types
import type {
// Comments types
CommentContextType,
CommentWithRelations,
Quote,
ProfileRow,
CoreCommentRow,
CoreCommentReactionRow,
CoreCommentReadRow,
CommentProviderProps,
} from "@pol-studios/features/comments";Related Packages
- @pol-studios/db - Database layer (required peer dependency)
- @pol-studios/db/auth - Authentication (required peer dependency)
- @pol-studios/utils - Utility functions (required peer dependency)
- @pol-studios/ui - UI components for rendering features
License
UNLICENSED
