@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,34 @@
|
|
|
1
|
+
import { BookOpenIcon } from 'lucide-react'
|
|
2
|
+
|
|
3
|
+
import { MainWorkspace, type MainWorkspaceProps } from '../main-workspace'
|
|
4
|
+
|
|
5
|
+
export type DocumentationWorkspaceProps = MainWorkspaceProps
|
|
6
|
+
|
|
7
|
+
/** Shared MDI workspace defaults for documentation and knowledge applications. */
|
|
8
|
+
export function DocumentationWorkspace({
|
|
9
|
+
applicationIcon = BookOpenIcon,
|
|
10
|
+
applicationId = 'docs',
|
|
11
|
+
applicationName = 'Docs',
|
|
12
|
+
primaryAction = null,
|
|
13
|
+
searchPlaceholder = 'Search documentation',
|
|
14
|
+
sidebarContentClassName = 'docs-sidebar-scroll',
|
|
15
|
+
sidebarStateKey = `codexsun.${applicationId}.sidebar`,
|
|
16
|
+
statusLabel = 'Ready',
|
|
17
|
+
workspaceTitle = 'Documentation',
|
|
18
|
+
...props
|
|
19
|
+
}: DocumentationWorkspaceProps) {
|
|
20
|
+
return (
|
|
21
|
+
<MainWorkspace
|
|
22
|
+
{...props}
|
|
23
|
+
applicationIcon={applicationIcon}
|
|
24
|
+
applicationId={applicationId}
|
|
25
|
+
applicationName={applicationName}
|
|
26
|
+
primaryAction={primaryAction}
|
|
27
|
+
searchPlaceholder={searchPlaceholder}
|
|
28
|
+
sidebarContentClassName={sidebarContentClassName}
|
|
29
|
+
sidebarStateKey={sidebarStateKey}
|
|
30
|
+
statusLabel={statusLabel}
|
|
31
|
+
workspaceTitle={workspaceTitle}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DocumentationWorkspace, type DocumentationWorkspaceProps } from './documentation-workspace'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { HeartIcon, ShoppingBagIcon, UserIcon } from "lucide-react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../../components/button";
|
|
5
|
+
import type { EcommerceHeaderActions } from "./ecommerce-header-types";
|
|
6
|
+
|
|
7
|
+
type EcommerceActionsProps = {
|
|
8
|
+
actions?: EcommerceHeaderActions;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function EcommerceActions({ actions }: EcommerceActionsProps) {
|
|
12
|
+
const cartCount = actions?.cartCount ?? 0;
|
|
13
|
+
const wishlistCount = actions?.wishlistCount ?? 0;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex items-center gap-1.5 sm:gap-2">
|
|
17
|
+
<CountAction count={wishlistCount} label="Wishlist" onClick={actions?.onWishlistClick}>
|
|
18
|
+
<HeartIcon className="size-5" />
|
|
19
|
+
</CountAction>
|
|
20
|
+
<CartAction actions={actions} count={cartCount} />
|
|
21
|
+
<Button
|
|
22
|
+
aria-label="User account"
|
|
23
|
+
className="px-2.5 text-muted-foreground hover:text-foreground"
|
|
24
|
+
size="sm"
|
|
25
|
+
variant="ghost"
|
|
26
|
+
onClick={actions?.onAccountClick}
|
|
27
|
+
>
|
|
28
|
+
<UserIcon className="size-5" />
|
|
29
|
+
<span className="ml-1.5 hidden text-xs font-medium xl:inline-block">Account</span>
|
|
30
|
+
</Button>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function CountAction({
|
|
36
|
+
children,
|
|
37
|
+
count,
|
|
38
|
+
label,
|
|
39
|
+
onClick,
|
|
40
|
+
}: {
|
|
41
|
+
children: ReactNode;
|
|
42
|
+
count: number;
|
|
43
|
+
label: string;
|
|
44
|
+
onClick?: () => void;
|
|
45
|
+
}) {
|
|
46
|
+
return (
|
|
47
|
+
<Button
|
|
48
|
+
aria-label={label}
|
|
49
|
+
className="relative px-2.5 text-muted-foreground hover:text-foreground"
|
|
50
|
+
size="sm"
|
|
51
|
+
variant="ghost"
|
|
52
|
+
onClick={onClick}
|
|
53
|
+
>
|
|
54
|
+
{children}
|
|
55
|
+
{count > 0 ? (
|
|
56
|
+
<span className="absolute -top-1 -right-1 flex size-4 items-center justify-center rounded-full bg-rose-600 text-[10px] font-bold text-white">
|
|
57
|
+
{count > 99 ? "99+" : count}
|
|
58
|
+
</span>
|
|
59
|
+
) : null}
|
|
60
|
+
<span className="ml-1.5 hidden text-xs font-medium xl:inline-block">{label}</span>
|
|
61
|
+
</Button>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function CartAction({ actions, count }: { actions?: EcommerceHeaderActions; count: number }) {
|
|
66
|
+
return (
|
|
67
|
+
<Button
|
|
68
|
+
aria-label="Shopping cart"
|
|
69
|
+
className="relative gap-2 px-3 shadow-xs"
|
|
70
|
+
size="sm"
|
|
71
|
+
onClick={actions?.onCartClick}
|
|
72
|
+
>
|
|
73
|
+
<span className="relative">
|
|
74
|
+
<ShoppingBagIcon className="size-4" />
|
|
75
|
+
{count > 0 ? (
|
|
76
|
+
<span className="absolute -top-2 -right-2 flex size-4 items-center justify-center rounded-full bg-accent text-[10px] font-bold text-accent-foreground">
|
|
77
|
+
{count > 99 ? "99+" : count}
|
|
78
|
+
</span>
|
|
79
|
+
) : null}
|
|
80
|
+
</span>
|
|
81
|
+
<span className="flex flex-col items-start text-left leading-none">
|
|
82
|
+
<span className="text-xs font-semibold">Cart</span>
|
|
83
|
+
{actions?.cartSubtotal ? (
|
|
84
|
+
<span className="text-[10px] font-medium opacity-90">{actions.cartSubtotal}</span>
|
|
85
|
+
) : null}
|
|
86
|
+
</span>
|
|
87
|
+
</Button>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SparklesIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import type { EcommerceHeaderProps } from "./ecommerce-header-types";
|
|
4
|
+
|
|
5
|
+
type EcommerceAnnouncementProps = {
|
|
6
|
+
announcement: NonNullable<EcommerceHeaderProps["announcement"]>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function EcommerceAnnouncement({ announcement }: EcommerceAnnouncementProps) {
|
|
10
|
+
const progress = Math.min(100, Math.max(0, announcement.freeShippingProgress ?? 75));
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className="relative border-b border-border/40 bg-muted/60 px-4 py-1.5 text-xs text-muted-foreground">
|
|
14
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4">
|
|
15
|
+
<div className="flex items-center gap-2">
|
|
16
|
+
<SparklesIcon className="size-3.5 text-primary" />
|
|
17
|
+
<span>{announcement.message}</span>
|
|
18
|
+
{announcement.actionLabel ? (
|
|
19
|
+
<a
|
|
20
|
+
className="font-semibold text-primary underline underline-offset-2 hover:opacity-80"
|
|
21
|
+
href={announcement.actionHref ?? "#"}
|
|
22
|
+
>
|
|
23
|
+
{announcement.actionLabel}
|
|
24
|
+
</a>
|
|
25
|
+
) : null}
|
|
26
|
+
</div>
|
|
27
|
+
{announcement.showFreeShippingMeter ? <ShippingMeter progress={progress} /> : null}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ShippingMeter({ progress }: { progress: number }) {
|
|
34
|
+
return (
|
|
35
|
+
<div className="hidden items-center gap-2 sm:flex">
|
|
36
|
+
<span>Free shipping on orders over $50</span>
|
|
37
|
+
<div className="h-1.5 w-24 overflow-hidden rounded-full bg-border">
|
|
38
|
+
<div className="h-full bg-primary transition-all duration-300" style={{ width: `${progress}%` }} />
|
|
39
|
+
</div>
|
|
40
|
+
<span className="font-semibold text-foreground">{progress}%</span>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { MenuIcon, SearchIcon, ShoppingBagIcon, XIcon } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Badge } from "../../components/badge";
|
|
5
|
+
import { Button } from "../../components/button";
|
|
6
|
+
import type { EcommerceCategoryItem, EcommerceHeaderActions } from "./ecommerce-header-types";
|
|
7
|
+
|
|
8
|
+
type EcommerceDrawerProps = {
|
|
9
|
+
actions?: EcommerceHeaderActions;
|
|
10
|
+
brandTitle: string;
|
|
11
|
+
cartCount: number;
|
|
12
|
+
categories: readonly EcommerceCategoryItem[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function EcommerceDrawer({ actions, brandTitle, cartCount, categories }: EcommerceDrawerProps) {
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
const [query, setQuery] = useState("");
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<Button
|
|
22
|
+
aria-label="Open storefront navigation"
|
|
23
|
+
className="size-9 md:hidden"
|
|
24
|
+
size="icon"
|
|
25
|
+
variant="ghost"
|
|
26
|
+
onClick={() => setOpen(true)}
|
|
27
|
+
>
|
|
28
|
+
<MenuIcon className="size-5" />
|
|
29
|
+
</Button>
|
|
30
|
+
{open ? (
|
|
31
|
+
<DrawerPanel
|
|
32
|
+
actions={actions}
|
|
33
|
+
brandTitle={brandTitle}
|
|
34
|
+
cartCount={cartCount}
|
|
35
|
+
categories={categories}
|
|
36
|
+
query={query}
|
|
37
|
+
onClose={() => setOpen(false)}
|
|
38
|
+
onQueryChange={setQuery}
|
|
39
|
+
/>
|
|
40
|
+
) : null}
|
|
41
|
+
</>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function DrawerPanel({
|
|
46
|
+
actions,
|
|
47
|
+
brandTitle,
|
|
48
|
+
cartCount,
|
|
49
|
+
categories,
|
|
50
|
+
query,
|
|
51
|
+
onClose,
|
|
52
|
+
onQueryChange,
|
|
53
|
+
}: EcommerceDrawerProps & { query: string; onClose: () => void; onQueryChange: (value: string) => void }) {
|
|
54
|
+
return (
|
|
55
|
+
<div className="fixed inset-0 z-50 flex bg-background/80 backdrop-blur-sm md:hidden">
|
|
56
|
+
<div className="flex w-full max-w-xs flex-col justify-between border-r border-border/80 bg-background p-6 shadow-2xl">
|
|
57
|
+
<div className="space-y-6">
|
|
58
|
+
<div className="flex items-center justify-between">
|
|
59
|
+
<span className="text-lg font-bold">{brandTitle}</span>
|
|
60
|
+
<Button aria-label="Close storefront navigation" size="icon" variant="ghost" onClick={onClose}>
|
|
61
|
+
<XIcon className="size-5" />
|
|
62
|
+
</Button>
|
|
63
|
+
</div>
|
|
64
|
+
<div className="flex items-center rounded-lg border border-input bg-muted/40 px-3 py-1.5">
|
|
65
|
+
<SearchIcon className="mr-2 size-4 text-muted-foreground" />
|
|
66
|
+
<input
|
|
67
|
+
className="w-full bg-transparent text-sm focus:outline-none"
|
|
68
|
+
placeholder="Search store..."
|
|
69
|
+
type="search"
|
|
70
|
+
value={query}
|
|
71
|
+
onChange={(event) => onQueryChange(event.target.value)}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
<div className="space-y-1.5">
|
|
75
|
+
<p className="text-xs font-semibold tracking-wider text-muted-foreground uppercase">Categories</p>
|
|
76
|
+
{categories.map((category) => (
|
|
77
|
+
<a
|
|
78
|
+
className="flex items-center justify-between rounded-lg p-2 text-sm font-medium text-foreground hover:bg-muted"
|
|
79
|
+
href={category.href}
|
|
80
|
+
key={category.id}
|
|
81
|
+
>
|
|
82
|
+
<span>{category.label}</span>
|
|
83
|
+
{category.isHot ? <Badge variant="destructive">Hot</Badge> : null}
|
|
84
|
+
</a>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="space-y-2 border-t border-border pt-6">
|
|
89
|
+
<Button className="w-full gap-2" onClick={actions?.onCartClick}>
|
|
90
|
+
<ShoppingBagIcon className="size-4" />
|
|
91
|
+
<span>View Cart ({cartCount})</span>
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export type EcommerceCurrency = {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly symbol: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type EcommerceCategoryItem = {
|
|
10
|
+
readonly href: string;
|
|
11
|
+
readonly icon?: ReactNode;
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly isHot?: boolean;
|
|
14
|
+
readonly isNew?: boolean;
|
|
15
|
+
readonly label: string;
|
|
16
|
+
readonly subcategories?: readonly {
|
|
17
|
+
readonly description?: string;
|
|
18
|
+
readonly href: string;
|
|
19
|
+
readonly label: string;
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type EcommerceHeaderActions = {
|
|
24
|
+
readonly cartCount?: number;
|
|
25
|
+
readonly cartSubtotal?: string;
|
|
26
|
+
readonly currency?: string;
|
|
27
|
+
readonly isSearching?: boolean;
|
|
28
|
+
readonly onAccountClick?: () => void;
|
|
29
|
+
readonly onCartClick?: () => void;
|
|
30
|
+
readonly onCurrencyChange?: (currency: string) => void;
|
|
31
|
+
readonly onSearch?: (query: string) => void;
|
|
32
|
+
readonly onWishlistClick?: () => void;
|
|
33
|
+
readonly wishlistCount?: number;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type EcommerceHeaderProps = {
|
|
37
|
+
readonly actions?: EcommerceHeaderActions;
|
|
38
|
+
readonly announcement?: {
|
|
39
|
+
readonly actionHref?: string;
|
|
40
|
+
readonly actionLabel?: string;
|
|
41
|
+
readonly freeShippingProgress?: number;
|
|
42
|
+
readonly message: string;
|
|
43
|
+
readonly showFreeShippingMeter?: boolean;
|
|
44
|
+
};
|
|
45
|
+
readonly brand: {
|
|
46
|
+
readonly href?: string;
|
|
47
|
+
readonly logo?: ReactNode;
|
|
48
|
+
readonly title: string;
|
|
49
|
+
};
|
|
50
|
+
readonly categories?: readonly EcommerceCategoryItem[];
|
|
51
|
+
readonly className?: string;
|
|
52
|
+
readonly popularSearches?: readonly string[];
|
|
53
|
+
readonly quickLinks?: readonly {
|
|
54
|
+
readonly badge?: string;
|
|
55
|
+
readonly href: string;
|
|
56
|
+
readonly label: string;
|
|
57
|
+
}[];
|
|
58
|
+
readonly supportPhone?: string;
|
|
59
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils";
|
|
2
|
+
|
|
3
|
+
import { EcommerceActions } from "./ecommerce-actions";
|
|
4
|
+
import { EcommerceAnnouncement } from "./ecommerce-announcement";
|
|
5
|
+
import { EcommerceDrawer } from "./ecommerce-drawer";
|
|
6
|
+
import { EcommerceNavigation } from "./ecommerce-navigation";
|
|
7
|
+
import { EcommerceSearch } from "./ecommerce-search";
|
|
8
|
+
import type { EcommerceHeaderProps } from "./ecommerce-header-types";
|
|
9
|
+
|
|
10
|
+
const defaultSearches = ["Wireless Earbuds", "Mechanical Keyboard", "Ergonomic Desk", "Smart Watch"];
|
|
11
|
+
const defaultLinks = [
|
|
12
|
+
{ badge: "Hot", href: "#deals", label: "Flash Deals" },
|
|
13
|
+
{ href: "#bestsellers", label: "Best Sellers" },
|
|
14
|
+
{ href: "#new", label: "New Arrivals" },
|
|
15
|
+
{ href: "#brands", label: "Featured Brands" },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export function EcommerceHeader({
|
|
19
|
+
actions,
|
|
20
|
+
announcement,
|
|
21
|
+
brand,
|
|
22
|
+
categories = [],
|
|
23
|
+
className,
|
|
24
|
+
popularSearches = defaultSearches,
|
|
25
|
+
quickLinks = defaultLinks,
|
|
26
|
+
supportPhone = "1-800-CODEXSUN",
|
|
27
|
+
}: EcommerceHeaderProps) {
|
|
28
|
+
const cartCount = actions?.cartCount ?? 0;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<header className={cn("w-full border-b border-border/80 bg-background/95 backdrop-blur-md", className)}>
|
|
32
|
+
{announcement ? <EcommerceAnnouncement announcement={announcement} /> : null}
|
|
33
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4 px-4 py-3.5">
|
|
34
|
+
<div className="flex items-center gap-3">
|
|
35
|
+
<EcommerceDrawer actions={actions} brandTitle={brand.title} cartCount={cartCount} categories={categories} />
|
|
36
|
+
<a className="flex items-center gap-2.5 focus-visible:outline-none" href={brand.href ?? "/"}>
|
|
37
|
+
{brand.logo}
|
|
38
|
+
<span className="text-xl font-bold tracking-tight text-foreground">{brand.title}</span>
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
<EcommerceSearch actions={actions} popularSearches={popularSearches} />
|
|
42
|
+
<EcommerceActions actions={actions} />
|
|
43
|
+
</div>
|
|
44
|
+
<EcommerceNavigation categories={categories} quickLinks={quickLinks} supportPhone={supportPhone} />
|
|
45
|
+
</header>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ChevronDownIcon, PhoneCallIcon } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
import { Badge } from "../../components/badge";
|
|
4
|
+
import type { EcommerceCategoryItem, EcommerceHeaderProps } from "./ecommerce-header-types";
|
|
5
|
+
|
|
6
|
+
type EcommerceNavigationProps = {
|
|
7
|
+
categories: readonly EcommerceCategoryItem[];
|
|
8
|
+
quickLinks: NonNullable<EcommerceHeaderProps["quickLinks"]>;
|
|
9
|
+
supportPhone?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function EcommerceNavigation({ categories, quickLinks, supportPhone }: EcommerceNavigationProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="border-t border-border/40 bg-muted/20 px-4 py-1.5 text-xs">
|
|
15
|
+
<div className="mx-auto flex max-w-7xl items-center justify-between gap-4">
|
|
16
|
+
<div className="no-scrollbar flex items-center gap-1 overflow-x-auto py-0.5">
|
|
17
|
+
{categories.map((category) => (
|
|
18
|
+
<CategoryMenu category={category} key={category.id} />
|
|
19
|
+
))}
|
|
20
|
+
<div className="mx-1.5 h-4 w-px bg-border/60" />
|
|
21
|
+
{quickLinks.map((link) => (
|
|
22
|
+
<QuickLink href={link.href} key={link.label} label={link.label} badge={link.badge} />
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
{supportPhone ? <SupportPhone value={supportPhone} /> : null}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CategoryMenu({ category }: { category: EcommerceCategoryItem }) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="group relative">
|
|
34
|
+
<a
|
|
35
|
+
className="flex items-center gap-1.5 rounded-md px-2.5 py-1 font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground group-hover:bg-muted group-hover:text-primary"
|
|
36
|
+
href={category.href}
|
|
37
|
+
>
|
|
38
|
+
{category.icon}
|
|
39
|
+
<span>{category.label}</span>
|
|
40
|
+
{category.subcategories?.length ? <ChevronDownIcon className="size-3 text-muted-foreground" /> : null}
|
|
41
|
+
{category.isHot ? (
|
|
42
|
+
<Badge className="h-4 px-1 text-[9px] font-bold uppercase" variant="destructive">
|
|
43
|
+
Hot
|
|
44
|
+
</Badge>
|
|
45
|
+
) : null}
|
|
46
|
+
</a>
|
|
47
|
+
{category.subcategories?.length ? <SubcategoryMenu category={category} /> : null}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function SubcategoryMenu({ category }: { category: EcommerceCategoryItem }) {
|
|
53
|
+
return (
|
|
54
|
+
<div className="animate-in fade-in-0 zoom-in-95 invisible absolute top-full left-0 z-50 mt-1 w-64 rounded-xl border border-border/80 bg-popover p-3 opacity-0 shadow-xl transition-opacity group-hover:visible group-hover:opacity-100">
|
|
55
|
+
<p className="mb-2 text-[11px] font-semibold tracking-wider text-muted-foreground uppercase">
|
|
56
|
+
{category.label} subcategories
|
|
57
|
+
</p>
|
|
58
|
+
<div className="space-y-1">
|
|
59
|
+
{category.subcategories?.map((subcategory) => (
|
|
60
|
+
<a
|
|
61
|
+
className="flex flex-col rounded-lg p-1.5 text-foreground transition-colors hover:bg-muted"
|
|
62
|
+
href={subcategory.href}
|
|
63
|
+
key={subcategory.label}
|
|
64
|
+
>
|
|
65
|
+
<span className="text-xs font-medium">{subcategory.label}</span>
|
|
66
|
+
{subcategory.description ? (
|
|
67
|
+
<span className="text-[10px] text-muted-foreground">{subcategory.description}</span>
|
|
68
|
+
) : null}
|
|
69
|
+
</a>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function QuickLink({ badge, href, label }: { badge?: string; href: string; label: string }) {
|
|
77
|
+
return (
|
|
78
|
+
<a
|
|
79
|
+
className="flex items-center gap-1 whitespace-nowrap rounded-md px-2 py-1 font-medium text-muted-foreground transition-colors hover:text-foreground"
|
|
80
|
+
href={href}
|
|
81
|
+
>
|
|
82
|
+
<span>{label}</span>
|
|
83
|
+
{badge ? (
|
|
84
|
+
<span className="rounded bg-rose-500/10 px-1 py-0.5 text-[9px] font-semibold text-rose-500">{badge}</span>
|
|
85
|
+
) : null}
|
|
86
|
+
</a>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function SupportPhone({ value }: { value: string }) {
|
|
91
|
+
return (
|
|
92
|
+
<div className="hidden shrink-0 items-center gap-1.5 text-muted-foreground lg:flex">
|
|
93
|
+
<PhoneCallIcon className="size-3.5 text-primary" />
|
|
94
|
+
<span>Need help?</span>
|
|
95
|
+
<span className="font-semibold text-foreground">{value}</span>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { SearchIcon, XIcon } from "lucide-react";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
|
|
4
|
+
import { Button } from "../../components/button";
|
|
5
|
+
import { cn } from "../../lib/utils";
|
|
6
|
+
import type { EcommerceHeaderActions } from "./ecommerce-header-types";
|
|
7
|
+
|
|
8
|
+
type EcommerceSearchProps = {
|
|
9
|
+
actions?: EcommerceHeaderActions;
|
|
10
|
+
popularSearches: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function EcommerceSearch({ actions, popularSearches }: EcommerceSearchProps) {
|
|
14
|
+
const [query, setQuery] = useState("");
|
|
15
|
+
const [focused, setFocused] = useState(false);
|
|
16
|
+
|
|
17
|
+
function search(value = query) {
|
|
18
|
+
actions?.onSearch?.(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="relative hidden max-w-xl flex-1 md:flex">
|
|
23
|
+
<div
|
|
24
|
+
className={cn(
|
|
25
|
+
"flex w-full items-center rounded-xl border border-input bg-muted/30 px-3 py-1.5 transition-all",
|
|
26
|
+
focused && "border-ring bg-background ring-2 ring-ring/20",
|
|
27
|
+
)}
|
|
28
|
+
>
|
|
29
|
+
<SearchIcon className="mr-2 size-4 shrink-0 text-muted-foreground" />
|
|
30
|
+
<input
|
|
31
|
+
className="w-full bg-transparent text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
32
|
+
placeholder="Search products, brands, categories..."
|
|
33
|
+
type="search"
|
|
34
|
+
value={query}
|
|
35
|
+
onBlur={() => setTimeout(() => setFocused(false), 200)}
|
|
36
|
+
onChange={(event) => setQuery(event.target.value)}
|
|
37
|
+
onFocus={() => setFocused(true)}
|
|
38
|
+
onKeyDown={(event) => {
|
|
39
|
+
if (event.key === "Enter") search();
|
|
40
|
+
}}
|
|
41
|
+
/>
|
|
42
|
+
{query ? <ClearSearch onClear={() => setQuery("")} /> : null}
|
|
43
|
+
<Button className="h-7 px-3 text-xs" size="sm" onClick={() => search()}>
|
|
44
|
+
Search
|
|
45
|
+
</Button>
|
|
46
|
+
</div>
|
|
47
|
+
{focused ? (
|
|
48
|
+
<SearchSuggestions
|
|
49
|
+
items={popularSearches}
|
|
50
|
+
onSelect={(item) => {
|
|
51
|
+
setQuery(item);
|
|
52
|
+
search(item);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
) : null}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ClearSearch({ onClear }: { onClear: () => void }) {
|
|
61
|
+
return (
|
|
62
|
+
<button className="mr-1 text-muted-foreground hover:text-foreground" type="button" onClick={onClear}>
|
|
63
|
+
<XIcon className="size-3.5" />
|
|
64
|
+
</button>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function SearchSuggestions({ items, onSelect }: { items: readonly string[]; onSelect: (item: string) => void }) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="animate-in fade-in-0 zoom-in-95 absolute top-full right-0 left-0 z-50 mt-1.5 rounded-xl border border-border/80 bg-popover p-3 shadow-xl">
|
|
71
|
+
<p className="mb-2 text-[11px] font-semibold tracking-wider text-muted-foreground uppercase">Popular searches</p>
|
|
72
|
+
<div className="flex flex-wrap gap-1.5">
|
|
73
|
+
{items.map((item) => (
|
|
74
|
+
<button
|
|
75
|
+
key={item}
|
|
76
|
+
className="rounded-lg bg-muted px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-primary/10 hover:text-primary"
|
|
77
|
+
type="button"
|
|
78
|
+
onClick={() => onSelect(item)}
|
|
79
|
+
>
|
|
80
|
+
{item}
|
|
81
|
+
</button>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export { MainWorkspace, type MainWorkspaceProps } from '../mdi-main/mdi-main'
|
|
2
|
+
export { createDefaultMdiApps } from '../mdi-main/mdi-app-catalog'
|
|
3
|
+
export { MdiNotificationsMenu } from '../mdi-main/mdi-notifications-menu'
|
|
4
|
+
export { MdiAppSwitcher } from '../mdi-main/mdi-app-switcher'
|
|
5
|
+
export { MdiFeatureSettings } from '../mdi-main/mdi-feature-settings'
|
|
6
|
+
export { MdiGlobalSearch } from '../mdi-main/mdi-global-search'
|
|
7
|
+
export { MdiProfileMenu } from '../mdi-main/mdi-profile-menu'
|
|
8
|
+
export { MdiSidebar } from '../mdi-main/mdi-sidebar'
|
|
9
|
+
export { MdiStatusBar } from '../mdi-main/mdi-status-bar'
|
|
10
|
+
export { MdiTopMenu } from '../mdi-main/mdi-top-menu'
|
|
11
|
+
export { mdiTopologySections, useMdiTopology } from '../mdi-main/mdi-topology'
|
|
12
|
+
export type { MdiCanvas, MdiDensity } from '../mdi-main/mdi-tweak-panel'
|
|
13
|
+
export type {
|
|
14
|
+
MdiAppItem,
|
|
15
|
+
MdiFeatureKey,
|
|
16
|
+
MdiFeatures,
|
|
17
|
+
MdiNavigationItem,
|
|
18
|
+
MdiNavigationSection,
|
|
19
|
+
MdiNotification,
|
|
20
|
+
MdiPrimaryAction,
|
|
21
|
+
MdiSettingsContentProps,
|
|
22
|
+
MdiUser,
|
|
23
|
+
} from '../mdi-main/mdi-types'
|
|
24
|
+
export type {
|
|
25
|
+
AgentWorkspaceProps,
|
|
26
|
+
AgentWorkspaceRail,
|
|
27
|
+
AgentWorkspaceRailItem,
|
|
28
|
+
} from '../agent-workspace'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Mdi, type MdiProps } from "./mdi";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils";
|
|
4
|
+
|
|
5
|
+
export type MdiProps = {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function Mdi({ children, className, label = "MDI" }: MdiProps) {
|
|
12
|
+
return (
|
|
13
|
+
<section
|
|
14
|
+
aria-label={label}
|
|
15
|
+
className={cn("grid size-full min-h-0 place-items-center bg-background text-foreground", className)}
|
|
16
|
+
>
|
|
17
|
+
{children ?? <span className="text-sm font-medium text-muted-foreground">{label}</span>}
|
|
18
|
+
</section>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { createDefaultMdiApps } from './mdi-app-catalog'
|
|
2
|
+
export { MdiNotificationsMenu } from './mdi-notifications-menu'
|
|
3
|
+
export { MdiAppSwitcher } from './mdi-app-switcher'
|
|
4
|
+
export { MdiFeatureSettings } from './mdi-feature-settings'
|
|
5
|
+
export { MdiGlobalSearch } from './mdi-global-search'
|
|
6
|
+
export { MdiProfileMenu } from './mdi-profile-menu'
|
|
7
|
+
export { MdiSidebar } from './mdi-sidebar'
|
|
8
|
+
export { MdiStatusBar } from './mdi-status-bar'
|
|
9
|
+
export { MdiTopMenu } from './mdi-top-menu'
|
|
10
|
+
export { mdiTopologySections, useMdiTopology } from './mdi-topology'
|
|
11
|
+
export type { MdiCanvas, MdiDensity } from './mdi-tweak-panel'
|
|
12
|
+
export type {
|
|
13
|
+
MdiAppItem,
|
|
14
|
+
MdiFeatureKey,
|
|
15
|
+
MdiFeatures,
|
|
16
|
+
MdiNavigationItem,
|
|
17
|
+
MdiNavigationSection,
|
|
18
|
+
MdiNotification,
|
|
19
|
+
MdiPrimaryAction,
|
|
20
|
+
MdiSettingsContentProps,
|
|
21
|
+
MdiUser,
|
|
22
|
+
} from './mdi-types'
|
|
23
|
+
export type {
|
|
24
|
+
AgentWorkspaceProps,
|
|
25
|
+
AgentWorkspaceRail,
|
|
26
|
+
AgentWorkspaceRailItem,
|
|
27
|
+
} from '../agent-workspace'
|