@devxcrew/react-ui 0.0.0-stage → 0.1.7
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 +85 -2
- package/package.json +105 -4
- package/src/blocks/agent-chat-workspace/agent-chat-workspace.tsx +162 -0
- package/src/blocks/agent-chat-workspace/index.ts +2 -0
- package/src/blocks/agent-provider-settings/agent-provider-settings.tsx +183 -0
- package/src/blocks/agent-provider-settings/index.ts +2 -0
- package/src/blocks/agent-task-workspace/agent-task-workspace.tsx +48 -0
- package/src/blocks/agent-task-workspace/index.ts +2 -0
- package/src/blocks/analysis-context/analysis-context.tsx +6 -0
- package/src/blocks/analysis-context/index.ts +1 -0
- package/src/blocks/app-header/app-header.tsx +159 -0
- package/src/blocks/app-header/index.ts +2 -0
- package/src/blocks/archived-chat-workspace/archived-chat-workspace.tsx +29 -0
- package/src/blocks/archived-chat-workspace/index.ts +2 -0
- package/src/blocks/auth/README.md +32 -0
- package/src/blocks/auth/admin-login-page.tsx +15 -0
- package/src/blocks/auth/admin-password-forgot-page.tsx +5 -0
- package/src/blocks/auth/auth-page-layout.tsx +90 -0
- package/src/blocks/auth/client-login-page.tsx +20 -0
- package/src/blocks/auth/client-password-forgot-page.tsx +5 -0
- package/src/blocks/auth/identity-management-desk.tsx +530 -0
- package/src/blocks/auth/index.ts +12 -0
- package/src/blocks/auth/login-page.tsx +206 -0
- package/src/blocks/auth/password-forgot-page.tsx +60 -0
- package/src/blocks/auth/portal-pages.tsx +32 -0
- package/src/blocks/auth/privileged-desk.tsx +55 -0
- package/src/blocks/auth/register-page.tsx +117 -0
- package/src/blocks/auth/session-boundary.tsx +296 -0
- package/src/blocks/auth/super-admin-login-page.tsx +15 -0
- package/src/blocks/auth/super-admin-password-forgot-page.tsx +5 -0
- package/src/blocks/blog/blog-card.tsx +156 -0
- package/src/blocks/blog/blog-reader.tsx +175 -0
- package/src/blocks/blog/blog-types.ts +47 -0
- package/src/blocks/blog/index.ts +3 -0
- package/src/blocks/chat-composer/chat-composer.tsx +140 -0
- package/src/blocks/chat-composer/index.ts +1 -0
- package/src/blocks/chat-history/chat-history.tsx +59 -0
- package/src/blocks/chat-history/index.ts +1 -0
- package/src/blocks/chat-response-progress/chat-response-progress.tsx +34 -0
- package/src/blocks/chat-response-progress/index.ts +2 -0
- package/src/blocks/chat-runtime-controls/chat-runtime-controls.tsx +56 -0
- package/src/blocks/chat-runtime-controls/index.ts +2 -0
- package/src/blocks/chat-runtime-trace/chat-runtime-trace.tsx +71 -0
- package/src/blocks/chat-runtime-trace/index.ts +2 -0
- package/src/blocks/codex-connection-settings/codex-connection-settings.tsx +42 -0
- package/src/blocks/codex-connection-settings/index.ts +1 -0
- package/src/blocks/dropzone/dropzone-file-item.tsx +113 -0
- package/src/blocks/dropzone/dropzone-types.ts +32 -0
- package/src/blocks/dropzone/dropzone.tsx +216 -0
- package/src/blocks/dropzone/index.ts +3 -0
- package/src/blocks/ecommerce/cart/cart-drawer.tsx +228 -0
- package/src/blocks/ecommerce/cart/cart-types.ts +27 -0
- package/src/blocks/ecommerce/cart/index.ts +2 -0
- package/src/blocks/ecommerce/categories/categories-types.ts +23 -0
- package/src/blocks/ecommerce/categories/category-showcase.tsx +125 -0
- package/src/blocks/ecommerce/categories/index.ts +2 -0
- package/src/blocks/ecommerce/checkout/checkout-types.ts +48 -0
- package/src/blocks/ecommerce/checkout/checkout-wizard.tsx +468 -0
- package/src/blocks/ecommerce/checkout/index.ts +2 -0
- package/src/blocks/ecommerce/comparison/comparison-types.ts +28 -0
- package/src/blocks/ecommerce/comparison/index.ts +2 -0
- package/src/blocks/ecommerce/comparison/product-comparison.tsx +174 -0
- package/src/blocks/ecommerce/coupon-wallet/coupon-wallet-types.ts +21 -0
- package/src/blocks/ecommerce/coupon-wallet/coupon-wallet.tsx +154 -0
- package/src/blocks/ecommerce/coupon-wallet/index.ts +2 -0
- package/src/blocks/ecommerce/delivery-tracker/delivery-tracker-types.ts +20 -0
- package/src/blocks/ecommerce/delivery-tracker/delivery-tracker.tsx +169 -0
- package/src/blocks/ecommerce/delivery-tracker/index.ts +2 -0
- package/src/blocks/ecommerce/index.ts +12 -0
- package/src/blocks/ecommerce/payment-methods/index.ts +2 -0
- package/src/blocks/ecommerce/payment-methods/payment-method-selector.tsx +224 -0
- package/src/blocks/ecommerce/payment-methods/payment-methods-types.ts +21 -0
- package/src/blocks/ecommerce/price-history/index.ts +2 -0
- package/src/blocks/ecommerce/price-history/price-history-chart.tsx +217 -0
- package/src/blocks/ecommerce/price-history/price-history-types.ts +16 -0
- package/src/blocks/ecommerce/reviews/index.ts +2 -0
- package/src/blocks/ecommerce/reviews/reviews-section.tsx +309 -0
- package/src/blocks/ecommerce/reviews/reviews-types.ts +26 -0
- package/src/blocks/ecommerce/wishlist/index.ts +2 -0
- package/src/blocks/ecommerce/wishlist/wishlist-grid.tsx +149 -0
- package/src/blocks/ecommerce/wishlist/wishlist-types.ts +19 -0
- package/src/blocks/execution-status/README.md +25 -0
- package/src/blocks/execution-status/execution-status.tsx +135 -0
- package/src/blocks/execution-status/index.ts +2 -0
- package/src/blocks/file-tree/file-icons.tsx +84 -0
- package/src/blocks/file-tree/file-tree-node.tsx +158 -0
- package/src/blocks/file-tree/file-tree-types.ts +33 -0
- package/src/blocks/file-tree/file-tree.tsx +163 -0
- package/src/blocks/file-tree/index.ts +5 -0
- package/src/blocks/filter-builder/filter-builder.tsx +169 -0
- package/src/blocks/filter-builder/filter-row.tsx +164 -0
- package/src/blocks/filter-builder/filter-types.ts +61 -0
- package/src/blocks/filter-builder/index.ts +12 -0
- package/src/blocks/footer/footer-types.ts +35 -0
- package/src/blocks/footer/index.ts +2 -0
- package/src/blocks/footer/site-footer.tsx +212 -0
- package/src/blocks/form/form-block.tsx +113 -0
- package/src/blocks/form/form-lookup-field.tsx +60 -0
- package/src/blocks/form/index.ts +4 -0
- package/src/blocks/handover-stack/handover-stack.tsx +41 -0
- package/src/blocks/handover-stack/index.ts +1 -0
- package/src/blocks/idea-handover/idea-handover-readiness.ts +41 -0
- package/src/blocks/idea-handover/idea-handover-workspace.tsx +150 -0
- package/src/blocks/idea-handover/index.ts +4 -0
- package/src/blocks/kanban/index.ts +11 -0
- package/src/blocks/kanban/kanban-board.tsx +146 -0
- package/src/blocks/kanban/kanban-card.tsx +149 -0
- package/src/blocks/kanban/kanban-column.tsx +102 -0
- package/src/blocks/kanban/kanban-types.ts +38 -0
- package/src/blocks/loader/global-loader.tsx +90 -0
- package/src/blocks/loader/index.ts +1 -0
- package/src/blocks/mascot/README.md +21 -0
- package/src/blocks/mascot/index.ts +2 -0
- package/src/blocks/mascot/mascot.tsx +202 -0
- package/src/blocks/master-list/README.md +21 -0
- package/src/blocks/master-list/index.ts +12 -0
- package/src/blocks/master-list/master-form.tsx +157 -0
- package/src/blocks/master-list/master-list-desk.tsx +370 -0
- package/src/blocks/master-list/master-list.tsx +189 -0
- package/src/blocks/master-list/types.ts +18 -0
- package/src/blocks/mermaid-preview/index.ts +1 -0
- package/src/blocks/mermaid-preview/mermaid-preview.tsx +164 -0
- package/src/blocks/notifications/README.md +7 -0
- package/src/blocks/notifications/index.ts +1 -0
- package/src/blocks/notifications/notification-center-page.tsx +73 -0
- package/src/blocks/pricing/index.ts +7 -0
- package/src/blocks/pricing/pricing-table.tsx +173 -0
- package/src/blocks/pricing/pricing-types.ts +34 -0
- package/src/blocks/product-card/index.ts +2 -0
- package/src/blocks/product-card/product-card-types.ts +35 -0
- package/src/blocks/product-card/product-card.tsx +183 -0
- package/src/blocks/table/data-table-block.tsx +313 -0
- package/src/blocks/table/data-table-filter-menu.tsx +48 -0
- package/src/blocks/table/data-table-icon-menu-trigger.tsx +44 -0
- package/src/blocks/table/data-table-menu-content.tsx +32 -0
- package/src/blocks/table/data-table-pagination.tsx +149 -0
- package/src/blocks/table/data-table-row-actions.tsx +53 -0
- package/src/blocks/table/data-table-status.tsx +42 -0
- package/src/blocks/table/data-table-toolbar.tsx +124 -0
- package/src/blocks/table/data-table-totals.tsx +31 -0
- package/src/blocks/table/index.ts +13 -0
- package/src/blocks/workspace/index.ts +19 -0
- package/src/blocks/workspace/workspace-action-card.tsx +73 -0
- package/src/blocks/workspace/workspace-application-header.tsx +67 -0
- package/src/blocks/workspace/workspace-entity-card.tsx +48 -0
- package/src/blocks/workspace/workspace-health-summary.tsx +15 -0
- package/src/blocks/workspace/workspace-metric-card.tsx +97 -0
- package/src/blocks/workspace/workspace-page-header.tsx +43 -0
- package/src/blocks/workspace/workspace-publish-status.tsx +15 -0
- package/src/blocks/workspace/workspace-route-checklist.tsx +28 -0
- package/src/blocks/workspace/workspace-runtime-status.tsx +48 -0
- package/src/blocks/workspace/workspace-section-card.tsx +42 -0
- package/src/components/accordion.tsx +65 -0
- package/src/components/alert-dialog.tsx +160 -0
- package/src/components/alert.tsx +68 -0
- package/src/components/aspect-ratio.tsx +22 -0
- package/src/components/attachment.tsx +195 -0
- package/src/components/avatar.tsx +90 -0
- package/src/components/badge.tsx +55 -0
- package/src/components/breadcrumb.tsx +100 -0
- package/src/components/bubble.tsx +123 -0
- package/src/components/button-group.tsx +79 -0
- package/src/components/button.tsx +71 -0
- package/src/components/calendar.tsx +185 -0
- package/src/components/card.tsx +100 -0
- package/src/components/carousel.tsx +229 -0
- package/src/components/chart.tsx +338 -0
- package/src/components/checkbox.tsx +27 -0
- package/src/components/collapsible.tsx +15 -0
- package/src/components/combobox.tsx +269 -0
- package/src/components/command.tsx +181 -0
- package/src/components/compact-model-switcher.tsx +128 -0
- package/src/components/context-menu.tsx +244 -0
- package/src/components/dialog.tsx +138 -0
- package/src/components/direction.tsx +1 -0
- package/src/components/drawer.tsx +208 -0
- package/src/components/dropdown-menu.tsx +257 -0
- package/src/components/empty.tsx +93 -0
- package/src/components/field.tsx +232 -0
- package/src/components/hover-card.tsx +45 -0
- package/src/components/input-group.tsx +146 -0
- package/src/components/input-otp.tsx +83 -0
- package/src/components/input.tsx +19 -0
- package/src/components/item.tsx +188 -0
- package/src/components/kbd.tsx +26 -0
- package/src/components/label.tsx +17 -0
- package/src/components/markdown-content.tsx +113 -0
- package/src/components/marker.tsx +67 -0
- package/src/components/menubar.tsx +267 -0
- package/src/components/message-scroller.tsx +91 -0
- package/src/components/message.tsx +84 -0
- package/src/components/native-select.tsx +54 -0
- package/src/components/navigation-menu.tsx +160 -0
- package/src/components/pagination.tsx +118 -0
- package/src/components/popover.tsx +74 -0
- package/src/components/progress.tsx +82 -0
- package/src/components/questionnaire.tsx +150 -0
- package/src/components/radio-group.tsx +35 -0
- package/src/components/resizable.tsx +41 -0
- package/src/components/rich-text-editor.tsx +604 -0
- package/src/components/scroll-area.tsx +47 -0
- package/src/components/select.tsx +189 -0
- package/src/components/separator.tsx +18 -0
- package/src/components/sheet.tsx +125 -0
- package/src/components/sidebar.tsx +693 -0
- package/src/components/skeleton.tsx +13 -0
- package/src/components/slider.tsx +51 -0
- package/src/components/sonner.tsx +45 -0
- package/src/components/sparkline.tsx +231 -0
- package/src/components/spinner.tsx +20 -0
- package/src/components/status-badge.tsx +114 -0
- package/src/components/switch.tsx +31 -0
- package/src/components/table.tsx +91 -0
- package/src/components/tabs.tsx +72 -0
- package/src/components/textarea.tsx +17 -0
- package/src/components/toast.tsx +222 -0
- package/src/components/toggle-group.tsx +85 -0
- package/src/components/toggle.tsx +42 -0
- package/src/components/tooltip.tsx +53 -0
- package/src/design-system/README.md +55 -0
- package/src/design-system/action-intents.ts +21 -0
- package/src/design-system/agent-manifest.ts +83 -0
- package/src/design-system/block-registry.ts +272 -0
- package/src/design-system/component-registry.ts +142 -0
- package/src/design-system/contracts.ts +69 -0
- package/src/design-system/defaults.ts +3 -0
- package/src/design-system/index.ts +29 -0
- package/src/design-system/mcp-server.ts +156 -0
- package/src/design-system/page-registry.ts +74 -0
- package/src/design-system/selection.ts +67 -0
- package/src/design-system/template-registry.ts +33 -0
- package/src/features/interface-topology/index.ts +14 -0
- package/src/features/interface-topology/interface-topology.types.ts +40 -0
- package/src/features/interface-topology/topology-control.tsx +18 -0
- package/src/features/interface-topology/topology-inspector.tsx +228 -0
- package/src/features/interface-topology/topology-marker.tsx +47 -0
- package/src/features/interface-topology/topology-region.tsx +44 -0
- package/src/features/interface-topology/use-interface-topology.ts +178 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-scroll-follow.ts +22 -0
- package/src/index.ts +11 -0
- package/src/layouts/README.md +65 -0
- package/src/layouts/agent-workspace/agent-activity-rail.tsx +101 -0
- package/src/layouts/agent-workspace/agent-workspace.tsx +27 -0
- package/src/layouts/agent-workspace/agent-workspace.types.ts +28 -0
- package/src/layouts/agent-workspace/index.ts +7 -0
- package/src/layouts/blog-header/blog-header-types.ts +25 -0
- package/src/layouts/blog-header/blog-header.tsx +172 -0
- package/src/layouts/blog-header/index.ts +2 -0
- package/src/layouts/documentation-workspace/README.md +8 -0
- package/src/layouts/documentation-workspace/documentation-workspace.tsx +34 -0
- package/src/layouts/documentation-workspace/index.ts +1 -0
- package/src/layouts/ecommerce-header/ecommerce-actions.tsx +89 -0
- package/src/layouts/ecommerce-header/ecommerce-announcement.tsx +43 -0
- package/src/layouts/ecommerce-header/ecommerce-drawer.tsx +97 -0
- package/src/layouts/ecommerce-header/ecommerce-header-types.ts +59 -0
- package/src/layouts/ecommerce-header/ecommerce-header.tsx +47 -0
- package/src/layouts/ecommerce-header/ecommerce-navigation.tsx +98 -0
- package/src/layouts/ecommerce-header/ecommerce-search.tsx +86 -0
- package/src/layouts/ecommerce-header/index.ts +2 -0
- package/src/layouts/main-workspace/index.ts +28 -0
- package/src/layouts/mdi/index.ts +1 -0
- package/src/layouts/mdi/mdi.tsx +20 -0
- package/src/layouts/mdi-main/index.ts +27 -0
- package/src/layouts/mdi-main/mdi-app-catalog.generated.ts +159 -0
- package/src/layouts/mdi-main/mdi-app-catalog.ts +76 -0
- package/src/layouts/mdi-main/mdi-app-switcher.tsx +107 -0
- package/src/layouts/mdi-main/mdi-feature-settings.tsx +135 -0
- package/src/layouts/mdi-main/mdi-global-search.tsx +192 -0
- package/src/layouts/mdi-main/mdi-main.tsx +241 -0
- package/src/layouts/mdi-main/mdi-notifications-menu.tsx +197 -0
- package/src/layouts/mdi-main/mdi-profile-menu.tsx +79 -0
- package/src/layouts/mdi-main/mdi-sidebar-navigation.tsx +226 -0
- package/src/layouts/mdi-main/mdi-sidebar.tsx +110 -0
- package/src/layouts/mdi-main/mdi-status-bar.tsx +26 -0
- package/src/layouts/mdi-main/mdi-top-menu-control.ts +4 -0
- package/src/layouts/mdi-main/mdi-top-menu.tsx +117 -0
- package/src/layouts/mdi-main/mdi-topology.tsx +297 -0
- package/src/layouts/mdi-main/mdi-tweak-panel.tsx +104 -0
- package/src/layouts/mdi-main/mdi-types.ts +131 -0
- package/src/layouts/mdi-main/use-mdi-features.ts +36 -0
- package/src/layouts/mdi-main/use-mdi-sidebar-state.ts +59 -0
- package/src/layouts/site-header/announcement-bar.tsx +55 -0
- package/src/layouts/site-header/category-strip.tsx +41 -0
- package/src/layouts/site-header/index.ts +16 -0
- package/src/layouts/site-header/main-navbar.tsx +179 -0
- package/src/layouts/site-header/mobile-drawer.tsx +140 -0
- package/src/layouts/site-header/site-header-types.ts +63 -0
- package/src/layouts/site-header/site-header.tsx +58 -0
- package/src/lib/random-id.ts +19 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/pdf-provider.ts +137 -0
- package/src/styles/globals.css +374 -0
- package/src/templates/README.md +12 -0
- package/src/templates/dashboard-01/components/app-sidebar.tsx +177 -0
- package/src/templates/dashboard-01/components/chart-area-interactive.tsx +276 -0
- package/src/templates/dashboard-01/components/data-table-cell-viewer.tsx +213 -0
- package/src/templates/dashboard-01/components/data-table.tsx +577 -0
- package/src/templates/dashboard-01/components/nav-documents.tsx +79 -0
- package/src/templates/dashboard-01/components/nav-main.tsx +55 -0
- package/src/templates/dashboard-01/components/nav-secondary.tsx +39 -0
- package/src/templates/dashboard-01/components/nav-user.tsx +97 -0
- package/src/templates/dashboard-01/components/section-cards.tsx +99 -0
- package/src/templates/dashboard-01/components/site-header.tsx +14 -0
- package/src/templates/dashboard-01/data.json +614 -0
- package/src/templates/dashboard-01/index.ts +7 -0
- package/src/templates/documentation-sidebar/app-sidebar.tsx +95 -0
- package/src/templates/documentation-sidebar/index.ts +2 -0
- package/src/templates/sidebar-07/components/app-sidebar.tsx +175 -0
- package/src/templates/sidebar-07/components/nav-main.tsx +64 -0
- package/src/templates/sidebar-07/components/nav-projects.tsx +79 -0
- package/src/templates/sidebar-07/components/nav-user.tsx +105 -0
- package/src/templates/sidebar-07/components/team-switcher.tsx +94 -0
- package/src/templates/sidebar-07/index.ts +1 -0
- package/src/templates/ui-page/index.ts +7 -0
- package/src/templates/ui-page/ui-browser-frame.tsx +27 -0
- package/src/templates/ui-page/ui-template-code.tsx +48 -0
- package/src/templates/ui-page/ui-template-header.tsx +87 -0
- package/src/templates/ui-page/ui-template-navigation.tsx +60 -0
- package/src/templates/ui-page/ui-template-page.tsx +97 -0
- package/src/theme/index.ts +12 -0
- package/src/theme/theme-config.ts +30 -0
- package/src/theme/theme-provider.tsx +142 -0
- package/src/theme/theme-selector.tsx +75 -0
- package/src/tokens/index.ts +15 -0
- package/src/vite-env.d.ts +3 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ChevronsDownUp, ChevronsUpDown, Search } from 'lucide-react'
|
|
2
|
+
import { useMemo, useState } from 'react'
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
import { FileTreeNodeComponent } from './file-tree-node'
|
|
5
|
+
import type { FileTreeNode, FileTreeProps } from './file-tree-types'
|
|
6
|
+
|
|
7
|
+
function filterNodes(nodes: readonly FileTreeNode[], query: string): FileTreeNode[] {
|
|
8
|
+
const q = query.toLowerCase()
|
|
9
|
+
const result: FileTreeNode[] = []
|
|
10
|
+
|
|
11
|
+
for (const node of nodes) {
|
|
12
|
+
if (node.name.toLowerCase().includes(q)) {
|
|
13
|
+
result.push(node)
|
|
14
|
+
} else if (node.type === 'directory' && node.children) {
|
|
15
|
+
const filteredChildren = filterNodes(node.children, query)
|
|
16
|
+
if (filteredChildren.length > 0) {
|
|
17
|
+
result.push({
|
|
18
|
+
...node,
|
|
19
|
+
children: filteredChildren,
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return result
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getAllDirectoryIds(nodes: readonly FileTreeNode[]): string[] {
|
|
29
|
+
const ids: string[] = []
|
|
30
|
+
for (const node of nodes) {
|
|
31
|
+
if (node.type === 'directory') {
|
|
32
|
+
ids.push(node.id)
|
|
33
|
+
if (node.children) {
|
|
34
|
+
ids.push(...getAllDirectoryIds(node.children))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return ids
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function FileTree({
|
|
42
|
+
allowCreate = false,
|
|
43
|
+
allowDelete = false,
|
|
44
|
+
ariaLabel = 'File and workspace tree',
|
|
45
|
+
className,
|
|
46
|
+
defaultExpandedIds = [],
|
|
47
|
+
emptyMessage = 'No files found',
|
|
48
|
+
nodes,
|
|
49
|
+
onCreateFile,
|
|
50
|
+
onCreateFolder,
|
|
51
|
+
onDeleteNode,
|
|
52
|
+
onExpandChange,
|
|
53
|
+
onSelectNode,
|
|
54
|
+
renderNodeActions,
|
|
55
|
+
searchPlaceholder = 'Filter files...',
|
|
56
|
+
selectedNodeId,
|
|
57
|
+
showFilter = true,
|
|
58
|
+
}: FileTreeProps) {
|
|
59
|
+
const [expandedIds, setExpandedIds] = useState<Set<string>>(() => new Set(defaultExpandedIds))
|
|
60
|
+
const [searchQuery, setSearchQuery] = useState('')
|
|
61
|
+
|
|
62
|
+
const allDirIds = useMemo(() => getAllDirectoryIds(nodes), [nodes])
|
|
63
|
+
|
|
64
|
+
const filteredNodes = useMemo(() => {
|
|
65
|
+
if (!searchQuery.trim()) return nodes
|
|
66
|
+
return filterNodes(nodes, searchQuery.trim())
|
|
67
|
+
}, [nodes, searchQuery])
|
|
68
|
+
|
|
69
|
+
function handleToggleExpand(node: FileTreeNode) {
|
|
70
|
+
setExpandedIds((prev) => {
|
|
71
|
+
const next = new Set(prev)
|
|
72
|
+
const isExpanding = !next.has(node.id)
|
|
73
|
+
if (isExpanding) {
|
|
74
|
+
next.add(node.id)
|
|
75
|
+
} else {
|
|
76
|
+
next.delete(node.id)
|
|
77
|
+
}
|
|
78
|
+
onExpandChange?.(node, isExpanding)
|
|
79
|
+
return next
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function handleExpandAll() {
|
|
84
|
+
setExpandedIds(new Set(allDirIds))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleCollapseAll() {
|
|
88
|
+
setExpandedIds(new Set())
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div
|
|
93
|
+
aria-label={ariaLabel}
|
|
94
|
+
className={cn(
|
|
95
|
+
'flex flex-col rounded-2xl border border-border/80 bg-card p-2 text-card-foreground shadow-2xs',
|
|
96
|
+
className,
|
|
97
|
+
)}
|
|
98
|
+
role="tree"
|
|
99
|
+
>
|
|
100
|
+
{showFilter && (
|
|
101
|
+
<div className="mb-2 flex items-center justify-between gap-1.5 border-b border-border/40 pb-2 px-1">
|
|
102
|
+
<div className="relative flex-1">
|
|
103
|
+
<Search className="absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground/60" />
|
|
104
|
+
<input
|
|
105
|
+
aria-label="Filter tree nodes"
|
|
106
|
+
className="h-8 w-full rounded-lg border border-border/60 bg-muted/40 pl-8 pr-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:bg-background focus:outline-none focus:ring-2 focus:ring-ring/30"
|
|
107
|
+
placeholder={searchPlaceholder}
|
|
108
|
+
type="text"
|
|
109
|
+
value={searchQuery}
|
|
110
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div className="flex items-center gap-0.5">
|
|
115
|
+
<button
|
|
116
|
+
aria-label="Expand all folders"
|
|
117
|
+
className="rounded p-1 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground"
|
|
118
|
+
title="Expand all"
|
|
119
|
+
type="button"
|
|
120
|
+
onClick={handleExpandAll}
|
|
121
|
+
>
|
|
122
|
+
<ChevronsUpDown className="size-3.5" />
|
|
123
|
+
</button>
|
|
124
|
+
<button
|
|
125
|
+
aria-label="Collapse all folders"
|
|
126
|
+
className="rounded p-1 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-foreground"
|
|
127
|
+
title="Collapse all"
|
|
128
|
+
type="button"
|
|
129
|
+
onClick={handleCollapseAll}
|
|
130
|
+
>
|
|
131
|
+
<ChevronsDownUp className="size-3.5" />
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
<div className="flex-1 overflow-y-auto min-h-24">
|
|
138
|
+
{filteredNodes.length > 0 ? (
|
|
139
|
+
filteredNodes.map((node) => (
|
|
140
|
+
<FileTreeNodeComponent
|
|
141
|
+
allowCreate={allowCreate}
|
|
142
|
+
allowDelete={allowDelete}
|
|
143
|
+
expandedIds={searchQuery.trim() ? new Set(allDirIds) : expandedIds}
|
|
144
|
+
key={node.id}
|
|
145
|
+
node={node}
|
|
146
|
+
onCreateFile={onCreateFile}
|
|
147
|
+
onCreateFolder={onCreateFolder}
|
|
148
|
+
onDeleteNode={onDeleteNode}
|
|
149
|
+
onSelectNode={onSelectNode}
|
|
150
|
+
onToggleExpand={handleToggleExpand}
|
|
151
|
+
renderActions={renderNodeActions}
|
|
152
|
+
selectedNodeId={selectedNodeId}
|
|
153
|
+
/>
|
|
154
|
+
))
|
|
155
|
+
) : (
|
|
156
|
+
<div className="flex items-center justify-center py-6 text-xs text-muted-foreground">
|
|
157
|
+
{emptyMessage}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { FileTree } from './file-tree'
|
|
2
|
+
export { FileTreeNodeComponent } from './file-tree-node'
|
|
3
|
+
export { FileTypeIcon, getFileExtension } from './file-icons'
|
|
4
|
+
export type { FileTreeNode, FileTreeProps } from './file-tree-types'
|
|
5
|
+
export type { FileTreeNodeProps } from './file-tree-node'
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Filter, Plus, RotateCcw } from 'lucide-react'
|
|
2
|
+
import { Button } from '../../components/button'
|
|
3
|
+
import { cn } from '../../lib/utils'
|
|
4
|
+
import { FilterRow } from './filter-row'
|
|
5
|
+
import type { FilterBuilderProps, FilterCombinator, FilterRule } from './filter-types'
|
|
6
|
+
|
|
7
|
+
export function FilterBuilder({
|
|
8
|
+
allowClear = true,
|
|
9
|
+
ariaLabel = 'Filter builder',
|
|
10
|
+
className,
|
|
11
|
+
emptyMessage = 'No filter conditions applied. Add a rule to filter records.',
|
|
12
|
+
fields,
|
|
13
|
+
filter,
|
|
14
|
+
maxRules = 10,
|
|
15
|
+
onApply,
|
|
16
|
+
onChange,
|
|
17
|
+
onClear,
|
|
18
|
+
title = 'Filter rules',
|
|
19
|
+
}: FilterBuilderProps) {
|
|
20
|
+
function handleAddRule() {
|
|
21
|
+
if (filter.rules.length >= maxRules || fields.length === 0) return
|
|
22
|
+
const defaultField = fields[0]
|
|
23
|
+
const defaultOp =
|
|
24
|
+
defaultField.type === 'number'
|
|
25
|
+
? 'equals'
|
|
26
|
+
: defaultField.type === 'select'
|
|
27
|
+
? 'equals'
|
|
28
|
+
: defaultField.type === 'date'
|
|
29
|
+
? 'equals'
|
|
30
|
+
: 'contains'
|
|
31
|
+
|
|
32
|
+
const newRule: FilterRule = {
|
|
33
|
+
fieldId: defaultField.id,
|
|
34
|
+
id: `rule-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`,
|
|
35
|
+
operator: defaultOp,
|
|
36
|
+
value: '',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
onChange({
|
|
40
|
+
...filter,
|
|
41
|
+
rules: [...filter.rules, newRule],
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleUpdateRule(updatedRule: FilterRule) {
|
|
46
|
+
onChange({
|
|
47
|
+
...filter,
|
|
48
|
+
rules: filter.rules.map((r) => (r.id === updatedRule.id ? updatedRule : r)),
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function handleDeleteRule(ruleId: string) {
|
|
53
|
+
onChange({
|
|
54
|
+
...filter,
|
|
55
|
+
rules: filter.rules.filter((r) => r.id !== ruleId),
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function handleCombinatorToggle(combinator: FilterCombinator) {
|
|
60
|
+
onChange({
|
|
61
|
+
...filter,
|
|
62
|
+
combinator,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleClear() {
|
|
67
|
+
onChange({
|
|
68
|
+
...filter,
|
|
69
|
+
rules: [],
|
|
70
|
+
})
|
|
71
|
+
onClear?.()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
aria-label={ariaLabel}
|
|
77
|
+
className={cn(
|
|
78
|
+
'flex flex-col gap-3.5 rounded-2xl border border-border/80 bg-card p-4 text-card-foreground shadow-2xs',
|
|
79
|
+
className,
|
|
80
|
+
)}
|
|
81
|
+
>
|
|
82
|
+
{/* Header */}
|
|
83
|
+
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border/40 pb-3">
|
|
84
|
+
<div className="flex items-center gap-2">
|
|
85
|
+
<Filter className="size-4 text-muted-foreground" />
|
|
86
|
+
<h4 className="text-sm font-semibold tracking-tight text-foreground">{title}</h4>
|
|
87
|
+
<span className="rounded-full bg-muted px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
|
|
88
|
+
{filter.rules.length}
|
|
89
|
+
{maxRules ? ` / ${maxRules}` : ''}
|
|
90
|
+
</span>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{filter.rules.length > 1 && (
|
|
94
|
+
<div className="flex items-center rounded-lg border border-border/80 bg-muted/40 p-0.5">
|
|
95
|
+
<button
|
|
96
|
+
className={cn(
|
|
97
|
+
'rounded-md px-2.5 py-1 text-xs font-semibold transition-all',
|
|
98
|
+
filter.combinator === 'AND'
|
|
99
|
+
? 'bg-background text-foreground shadow-2xs'
|
|
100
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
101
|
+
)}
|
|
102
|
+
type="button"
|
|
103
|
+
onClick={() => handleCombinatorToggle('AND')}
|
|
104
|
+
>
|
|
105
|
+
Match ALL (AND)
|
|
106
|
+
</button>
|
|
107
|
+
<button
|
|
108
|
+
className={cn(
|
|
109
|
+
'rounded-md px-2.5 py-1 text-xs font-semibold transition-all',
|
|
110
|
+
filter.combinator === 'OR'
|
|
111
|
+
? 'bg-background text-foreground shadow-2xs'
|
|
112
|
+
: 'text-muted-foreground hover:text-foreground',
|
|
113
|
+
)}
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={() => handleCombinatorToggle('OR')}
|
|
116
|
+
>
|
|
117
|
+
Match ANY (OR)
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Rules list */}
|
|
124
|
+
<div className="flex flex-col gap-2 min-h-16">
|
|
125
|
+
{filter.rules.map((rule) => (
|
|
126
|
+
<FilterRow
|
|
127
|
+
fields={fields}
|
|
128
|
+
key={rule.id}
|
|
129
|
+
onDelete={handleDeleteRule}
|
|
130
|
+
onUpdate={handleUpdateRule}
|
|
131
|
+
rule={rule}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
|
|
135
|
+
{filter.rules.length === 0 && (
|
|
136
|
+
<div className="flex items-center justify-center rounded-xl border border-dashed border-border/60 py-6 text-center text-xs text-muted-foreground">
|
|
137
|
+
{emptyMessage}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Footer controls */}
|
|
143
|
+
<div className="flex flex-wrap items-center justify-between gap-2 border-t border-border/40 pt-3">
|
|
144
|
+
<Button
|
|
145
|
+
disabled={filter.rules.length >= maxRules}
|
|
146
|
+
size="sm"
|
|
147
|
+
variant="outline"
|
|
148
|
+
onClick={handleAddRule}
|
|
149
|
+
>
|
|
150
|
+
<Plus className="size-3.5" /> Add condition
|
|
151
|
+
</Button>
|
|
152
|
+
|
|
153
|
+
<div className="flex items-center gap-2">
|
|
154
|
+
{allowClear && filter.rules.length > 0 && (
|
|
155
|
+
<Button size="sm" variant="ghost" onClick={handleClear}>
|
|
156
|
+
<RotateCcw className="size-3.5" /> Clear
|
|
157
|
+
</Button>
|
|
158
|
+
)}
|
|
159
|
+
|
|
160
|
+
{onApply && (
|
|
161
|
+
<Button size="sm" variant="default" onClick={() => onApply(filter)}>
|
|
162
|
+
Apply filter
|
|
163
|
+
</Button>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)
|
|
169
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Trash2 } from 'lucide-react'
|
|
2
|
+
import { cn } from '../../lib/utils'
|
|
3
|
+
import type { FilterFieldDefinition, FilterOperator, FilterRule } from './filter-types'
|
|
4
|
+
|
|
5
|
+
const operatorsByType: Record<string, { label: string; value: FilterOperator }[]> = {
|
|
6
|
+
text: [
|
|
7
|
+
{ label: 'contains', value: 'contains' },
|
|
8
|
+
{ label: 'does not contain', value: 'not_contains' },
|
|
9
|
+
{ label: 'equals', value: 'equals' },
|
|
10
|
+
{ label: 'starts with', value: 'starts_with' },
|
|
11
|
+
{ label: 'ends with', value: 'ends_with' },
|
|
12
|
+
{ label: 'is empty', value: 'is_empty' },
|
|
13
|
+
{ label: 'is not empty', value: 'is_not_empty' },
|
|
14
|
+
],
|
|
15
|
+
number: [
|
|
16
|
+
{ label: '=', value: 'equals' },
|
|
17
|
+
{ label: '≠', value: 'not_equals' },
|
|
18
|
+
{ label: '>', value: 'greater_than' },
|
|
19
|
+
{ label: '<', value: 'less_than' },
|
|
20
|
+
{ label: '≥', value: 'greater_or_equal' },
|
|
21
|
+
{ label: '≤', value: 'less_or_equal' },
|
|
22
|
+
{ label: 'between', value: 'between' },
|
|
23
|
+
],
|
|
24
|
+
date: [
|
|
25
|
+
{ label: 'is on', value: 'equals' },
|
|
26
|
+
{ label: 'is after', value: 'greater_than' },
|
|
27
|
+
{ label: 'is before', value: 'less_than' },
|
|
28
|
+
{ label: 'between', value: 'between' },
|
|
29
|
+
],
|
|
30
|
+
select: [
|
|
31
|
+
{ label: 'is', value: 'equals' },
|
|
32
|
+
{ label: 'is not', value: 'not_equals' },
|
|
33
|
+
],
|
|
34
|
+
boolean: [
|
|
35
|
+
{ label: 'is true', value: 'is_true' },
|
|
36
|
+
{ label: 'is false', value: 'is_false' },
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function FilterRow({
|
|
41
|
+
fields,
|
|
42
|
+
onDelete,
|
|
43
|
+
onUpdate,
|
|
44
|
+
rule,
|
|
45
|
+
}: {
|
|
46
|
+
fields: readonly FilterFieldDefinition[]
|
|
47
|
+
onDelete: (id: string) => void
|
|
48
|
+
onUpdate: (updated: FilterRule) => void
|
|
49
|
+
rule: FilterRule
|
|
50
|
+
}) {
|
|
51
|
+
const currentField = fields.find((f) => f.id === rule.fieldId) || fields[0]
|
|
52
|
+
const fieldType = currentField?.type || 'text'
|
|
53
|
+
const operators = operatorsByType[fieldType] || operatorsByType.text
|
|
54
|
+
|
|
55
|
+
const isUnaryOperator =
|
|
56
|
+
rule.operator === 'is_empty' ||
|
|
57
|
+
rule.operator === 'is_not_empty' ||
|
|
58
|
+
rule.operator === 'is_true' ||
|
|
59
|
+
rule.operator === 'is_false'
|
|
60
|
+
|
|
61
|
+
const isBetween = rule.operator === 'between'
|
|
62
|
+
|
|
63
|
+
function handleFieldChange(newFieldId: string) {
|
|
64
|
+
const newField = fields.find((f) => f.id === newFieldId)
|
|
65
|
+
const newType = newField?.type || 'text'
|
|
66
|
+
const defaultOp = operatorsByType[newType]?.[0]?.value || 'contains'
|
|
67
|
+
onUpdate({
|
|
68
|
+
...rule,
|
|
69
|
+
fieldId: newFieldId,
|
|
70
|
+
operator: defaultOp,
|
|
71
|
+
value: '',
|
|
72
|
+
valueTo: '',
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className="flex flex-wrap items-center gap-2 rounded-xl border border-border/60 bg-background/50 p-2.5 transition-colors hover:border-border">
|
|
78
|
+
{/* Field selector */}
|
|
79
|
+
<select
|
|
80
|
+
aria-label="Filter field"
|
|
81
|
+
className="h-8.5 rounded-lg border border-border/80 bg-background px-2.5 text-xs font-medium text-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/30"
|
|
82
|
+
value={rule.fieldId}
|
|
83
|
+
onChange={(e) => handleFieldChange(e.target.value)}
|
|
84
|
+
>
|
|
85
|
+
{fields.map((f) => (
|
|
86
|
+
<option key={f.id} value={f.id}>
|
|
87
|
+
{f.label}
|
|
88
|
+
</option>
|
|
89
|
+
))}
|
|
90
|
+
</select>
|
|
91
|
+
|
|
92
|
+
{/* Operator selector */}
|
|
93
|
+
<select
|
|
94
|
+
aria-label="Filter operator"
|
|
95
|
+
className="h-8.5 rounded-lg border border-border/80 bg-background px-2.5 text-xs font-medium text-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/30"
|
|
96
|
+
value={rule.operator}
|
|
97
|
+
onChange={(e) => onUpdate({ ...rule, operator: e.target.value as FilterOperator })}
|
|
98
|
+
>
|
|
99
|
+
{operators.map((op) => (
|
|
100
|
+
<option key={op.value} value={op.value}>
|
|
101
|
+
{op.label}
|
|
102
|
+
</option>
|
|
103
|
+
))}
|
|
104
|
+
</select>
|
|
105
|
+
|
|
106
|
+
{/* Dynamic value input */}
|
|
107
|
+
{!isUnaryOperator && (
|
|
108
|
+
<div className="flex flex-1 items-center gap-2 min-w-36">
|
|
109
|
+
{fieldType === 'select' && currentField.options ? (
|
|
110
|
+
<select
|
|
111
|
+
aria-label="Filter value"
|
|
112
|
+
className="h-8.5 w-full rounded-lg border border-border/80 bg-background px-2.5 text-xs text-foreground focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/30"
|
|
113
|
+
value={String(rule.value ?? '')}
|
|
114
|
+
onChange={(e) => onUpdate({ ...rule, value: e.target.value })}
|
|
115
|
+
>
|
|
116
|
+
<option value="">Select an option...</option>
|
|
117
|
+
{currentField.options.map((opt) => (
|
|
118
|
+
<option key={opt.value} value={opt.value}>
|
|
119
|
+
{opt.label}
|
|
120
|
+
</option>
|
|
121
|
+
))}
|
|
122
|
+
</select>
|
|
123
|
+
) : (
|
|
124
|
+
<input
|
|
125
|
+
aria-label="Filter value"
|
|
126
|
+
className={cn(
|
|
127
|
+
'h-8.5 w-full rounded-lg border border-border/80 bg-background px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/30',
|
|
128
|
+
isBetween && 'w-1/2',
|
|
129
|
+
)}
|
|
130
|
+
placeholder={currentField?.placeholder || 'Enter value...'}
|
|
131
|
+
type={fieldType === 'number' ? 'number' : fieldType === 'date' ? 'date' : 'text'}
|
|
132
|
+
value={String(rule.value ?? '')}
|
|
133
|
+
onChange={(e) => onUpdate({ ...rule, value: e.target.value })}
|
|
134
|
+
/>
|
|
135
|
+
)}
|
|
136
|
+
|
|
137
|
+
{isBetween && (
|
|
138
|
+
<>
|
|
139
|
+
<span className="text-xs text-muted-foreground">and</span>
|
|
140
|
+
<input
|
|
141
|
+
aria-label="Filter value to"
|
|
142
|
+
className="h-8.5 w-1/2 rounded-lg border border-border/80 bg-background px-2.5 text-xs text-foreground placeholder:text-muted-foreground/60 focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring/30"
|
|
143
|
+
placeholder="To..."
|
|
144
|
+
type={fieldType === 'number' ? 'number' : fieldType === 'date' ? 'date' : 'text'}
|
|
145
|
+
value={String(rule.valueTo ?? '')}
|
|
146
|
+
onChange={(e) => onUpdate({ ...rule, valueTo: e.target.value })}
|
|
147
|
+
/>
|
|
148
|
+
</>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
|
|
153
|
+
{/* Delete button */}
|
|
154
|
+
<button
|
|
155
|
+
aria-label="Remove filter rule"
|
|
156
|
+
className="size-8.5 shrink-0 rounded-lg p-2 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
157
|
+
type="button"
|
|
158
|
+
onClick={() => onDelete(rule.id)}
|
|
159
|
+
>
|
|
160
|
+
<Trash2 className="size-4" />
|
|
161
|
+
</button>
|
|
162
|
+
</div>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type FilterFieldType = 'text' | 'number' | 'date' | 'select' | 'boolean'
|
|
2
|
+
|
|
3
|
+
export type FilterOperator =
|
|
4
|
+
| 'equals'
|
|
5
|
+
| 'not_equals'
|
|
6
|
+
| 'contains'
|
|
7
|
+
| 'not_contains'
|
|
8
|
+
| 'starts_with'
|
|
9
|
+
| 'ends_with'
|
|
10
|
+
| 'greater_than'
|
|
11
|
+
| 'less_than'
|
|
12
|
+
| 'greater_or_equal'
|
|
13
|
+
| 'less_or_equal'
|
|
14
|
+
| 'between'
|
|
15
|
+
| 'is_true'
|
|
16
|
+
| 'is_false'
|
|
17
|
+
| 'is_empty'
|
|
18
|
+
| 'is_not_empty'
|
|
19
|
+
| 'in'
|
|
20
|
+
|
|
21
|
+
export interface FilterFieldOption {
|
|
22
|
+
label: string
|
|
23
|
+
value: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FilterFieldDefinition {
|
|
27
|
+
id: string
|
|
28
|
+
label: string
|
|
29
|
+
options?: readonly FilterFieldOption[]
|
|
30
|
+
placeholder?: string
|
|
31
|
+
type: FilterFieldType
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface FilterRule {
|
|
35
|
+
fieldId: string
|
|
36
|
+
id: string
|
|
37
|
+
operator: FilterOperator
|
|
38
|
+
value?: unknown
|
|
39
|
+
valueTo?: unknown
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type FilterCombinator = 'AND' | 'OR'
|
|
43
|
+
|
|
44
|
+
export interface FilterGroup {
|
|
45
|
+
combinator: FilterCombinator
|
|
46
|
+
rules: readonly FilterRule[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface FilterBuilderProps {
|
|
50
|
+
allowClear?: boolean
|
|
51
|
+
ariaLabel?: string
|
|
52
|
+
className?: string
|
|
53
|
+
emptyMessage?: string
|
|
54
|
+
fields: readonly FilterFieldDefinition[]
|
|
55
|
+
filter: FilterGroup
|
|
56
|
+
maxRules?: number
|
|
57
|
+
onApply?: (filter: FilterGroup) => void
|
|
58
|
+
onChange: (filter: FilterGroup) => void
|
|
59
|
+
onClear?: () => void
|
|
60
|
+
title?: string
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { FilterBuilder } from './filter-builder'
|
|
2
|
+
export { FilterRow } from './filter-row'
|
|
3
|
+
export type {
|
|
4
|
+
FilterBuilderProps,
|
|
5
|
+
FilterCombinator,
|
|
6
|
+
FilterFieldDefinition,
|
|
7
|
+
FilterFieldOption,
|
|
8
|
+
FilterFieldType,
|
|
9
|
+
FilterGroup,
|
|
10
|
+
FilterOperator,
|
|
11
|
+
FilterRule,
|
|
12
|
+
} from './filter-types'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
export type FooterLink = {
|
|
4
|
+
readonly badge?: string
|
|
5
|
+
readonly href: string
|
|
6
|
+
readonly label: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type FooterColumn = {
|
|
10
|
+
readonly links: readonly FooterLink[]
|
|
11
|
+
readonly title: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type SiteFooterProps = {
|
|
15
|
+
readonly brand: {
|
|
16
|
+
readonly description?: string
|
|
17
|
+
readonly logo?: ReactNode
|
|
18
|
+
readonly title: string
|
|
19
|
+
}
|
|
20
|
+
readonly className?: string
|
|
21
|
+
readonly columns?: readonly FooterColumn[]
|
|
22
|
+
readonly copyright?: string
|
|
23
|
+
readonly newsletter?: {
|
|
24
|
+
readonly description?: string
|
|
25
|
+
readonly onSubscribe?: (email: string) => void
|
|
26
|
+
readonly placeholder?: string
|
|
27
|
+
readonly title?: string
|
|
28
|
+
}
|
|
29
|
+
readonly paymentBadges?: readonly string[]
|
|
30
|
+
readonly socialLinks?: readonly {
|
|
31
|
+
readonly href: string
|
|
32
|
+
readonly icon: ReactNode
|
|
33
|
+
readonly label: string
|
|
34
|
+
}[]
|
|
35
|
+
}
|