@object-ui/layout 3.3.0 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Page.tsx DELETED
@@ -1,39 +0,0 @@
1
- // packages/layout/src/Page.tsx
2
- import React from 'react';
3
- import { SchemaRenderer } from '@object-ui/react';
4
- import { PageSchema, SchemaNode } from '@object-ui/types';
5
- import { PageHeader } from './PageHeader';
6
- import { cn } from '@object-ui/components';
7
-
8
- // Helper to ensure children is always an array
9
- const getChildren = (children?: SchemaNode[] | SchemaNode): SchemaNode[] => {
10
- if (!children) return [];
11
- if (Array.isArray(children)) return children;
12
- return [children];
13
- };
14
-
15
- export function Page({ schema, className, style, id, ...props }: { schema: PageSchema; className?: string; style?: React.CSSProperties; id?: string } & any) {
16
- const children = getChildren(schema.children);
17
-
18
- return (
19
- <div
20
- id={id || schema.id}
21
- className={cn("flex flex-col h-full space-y-4", className)}
22
- style={style}
23
- >
24
- <PageHeader
25
- title={schema.title}
26
- description={schema.description}
27
- />
28
- <div className="flex-1 overflow-auto">
29
- {children.map((child: any, index: number) => (
30
- <SchemaRenderer
31
- key={child?.id || index}
32
- schema={child}
33
- {...props}
34
- />
35
- ))}
36
- </div>
37
- </div>
38
- );
39
- }
package/src/PageCard.tsx DELETED
@@ -1,12 +0,0 @@
1
- import React from 'react';
2
- import { cn } from '@object-ui/components';
3
-
4
- export function PageCard({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
5
- return (
6
- <div className={cn("rounded-xl border bg-card text-card-foreground shadow", className)} {...props}>
7
- <div className="p-6">
8
- {children}
9
- </div>
10
- </div>
11
- );
12
- }
@@ -1,35 +0,0 @@
1
- import React from 'react';
2
- import { cn } from '@object-ui/components';
3
-
4
- export interface PageHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
5
- title: string;
6
- description?: string;
7
- action?: React.ReactNode;
8
- }
9
-
10
- export function PageHeader({
11
- title,
12
- description,
13
- action,
14
- className,
15
- children,
16
- ...props
17
- }: PageHeaderProps) {
18
- return (
19
- <div className={cn("flex flex-col gap-4 pb-4 md:pb-8", className)} {...props}>
20
- <div className="flex items-center justify-between gap-4">
21
- <div className="flex flex-col gap-1">
22
- <h1 className="text-2xl font-bold tracking-tight md:text-3xl">{title}</h1>
23
- {description && <p className="text-sm text-muted-foreground">{description}</p>}
24
- </div>
25
- {/* Render children (actions) in the top-right slot if available */}
26
- {(action || children) && (
27
- <div className="flex items-center gap-2">
28
- {action}
29
- {children}
30
- </div>
31
- )}
32
- </div>
33
- </div>
34
- );
35
- }
@@ -1,118 +0,0 @@
1
- /**
2
- * ObjectUI
3
- * Copyright (c) 2024-present ObjectStack Inc.
4
- *
5
- * This source code is licensed under the MIT license found in the
6
- * LICENSE file in the root directory of this source tree.
7
- */
8
-
9
- import React from 'react';
10
- import { cn } from '@object-ui/components';
11
-
12
- /**
13
- * Spec-aligned breakpoint column map (mirrors @objectstack/spec BreakpointColumnMapSchema).
14
- * Maps breakpoint names to grid column counts (1-12).
15
- */
16
- export interface BreakpointColumnMap {
17
- xs?: number;
18
- sm?: number;
19
- md?: number;
20
- lg?: number;
21
- xl?: number;
22
- '2xl'?: number;
23
- }
24
-
25
- /**
26
- * Spec-aligned breakpoint order map (mirrors @objectstack/spec BreakpointOrderMapSchema).
27
- * Maps breakpoint names to display order numbers.
28
- */
29
- export interface BreakpointOrderMap {
30
- xs?: number;
31
- sm?: number;
32
- md?: number;
33
- lg?: number;
34
- xl?: number;
35
- '2xl'?: number;
36
- }
37
-
38
- export interface ResponsiveGridProps {
39
- /** Grid column map per breakpoint */
40
- columns?: BreakpointColumnMap;
41
- /** Gap between grid items */
42
- gap?: number | string;
43
- /** Additional class names */
44
- className?: string;
45
- /** Children */
46
- children: React.ReactNode;
47
- }
48
-
49
- /**
50
- * Tailwind class mapping for grid columns at each breakpoint.
51
- * Uses standard Tailwind grid-cols utilities for CSS-only responsiveness.
52
- */
53
- const COLS_CLASSES: Record<string, Record<number, string>> = {
54
- xs: { 1: 'grid-cols-1', 2: 'grid-cols-2', 3: 'grid-cols-3', 4: 'grid-cols-4', 6: 'grid-cols-6', 12: 'grid-cols-12' },
55
- sm: { 1: 'sm:grid-cols-1', 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3', 4: 'sm:grid-cols-4', 6: 'sm:grid-cols-6', 12: 'sm:grid-cols-12' },
56
- md: { 1: 'md:grid-cols-1', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3', 4: 'md:grid-cols-4', 6: 'md:grid-cols-6', 12: 'md:grid-cols-12' },
57
- lg: { 1: 'lg:grid-cols-1', 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3', 4: 'lg:grid-cols-4', 6: 'lg:grid-cols-6', 12: 'lg:grid-cols-12' },
58
- xl: { 1: 'xl:grid-cols-1', 2: 'xl:grid-cols-2', 3: 'xl:grid-cols-3', 4: 'xl:grid-cols-4', 6: 'xl:grid-cols-6', 12: 'xl:grid-cols-12' },
59
- '2xl': { 1: '2xl:grid-cols-1', 2: '2xl:grid-cols-2', 3: '2xl:grid-cols-3', 4: '2xl:grid-cols-4', 6: '2xl:grid-cols-6', 12: '2xl:grid-cols-12' },
60
- };
61
-
62
- /**
63
- * Resolve a BreakpointColumnMap into Tailwind CSS grid classes.
64
- */
65
- function resolveColumnClasses(columns?: BreakpointColumnMap): string {
66
- if (!columns) return 'grid-cols-1';
67
-
68
- const classes: string[] = [];
69
- for (const [bp, cols] of Object.entries(columns)) {
70
- const bpClasses = COLS_CLASSES[bp];
71
- if (bpClasses && cols) {
72
- // Use closest supported column count
73
- const supported = Object.keys(bpClasses).map(Number);
74
- const closest = supported.reduce((prev, curr) =>
75
- Math.abs(curr - cols) < Math.abs(prev - cols) ? curr : prev
76
- );
77
- classes.push(bpClasses[closest]);
78
- }
79
- }
80
-
81
- return classes.join(' ') || 'grid-cols-1';
82
- }
83
-
84
- const GAP_CLASSES: Record<number, string> = {
85
- 0: 'gap-0', 1: 'gap-1', 2: 'gap-2', 3: 'gap-3', 4: 'gap-4',
86
- 5: 'gap-5', 6: 'gap-6', 8: 'gap-8',
87
- };
88
-
89
- /**
90
- * ResponsiveGrid — A layout component that consumes @objectstack/spec
91
- * BreakpointColumnMapSchema for responsive grid layouts.
92
- *
93
- * Uses pure Tailwind CSS classes for responsive behavior (no JS resize listeners).
94
- *
95
- * @example
96
- * ```tsx
97
- * <ResponsiveGrid columns={{ xs: 1, sm: 2, lg: 3 }} gap={4}>
98
- * <Card>Item 1</Card>
99
- * <Card>Item 2</Card>
100
- * <Card>Item 3</Card>
101
- * </ResponsiveGrid>
102
- * ```
103
- */
104
- export const ResponsiveGrid: React.FC<ResponsiveGridProps> = ({
105
- columns,
106
- gap = 4,
107
- className,
108
- children,
109
- }) => {
110
- const colClasses = resolveColumnClasses(columns);
111
- const gapClass = typeof gap === 'number' ? (GAP_CLASSES[gap] || `gap-${gap}`) : '';
112
-
113
- return (
114
- <div className={cn('grid', colClasses, gapClass, className)}>
115
- {children}
116
- </div>
117
- );
118
- };
@@ -1,164 +0,0 @@
1
- import React from 'react';
2
- import { NavLink, useLocation } from 'react-router-dom';
3
- import {
4
- Sidebar,
5
- SidebarContent,
6
- SidebarGroup,
7
- SidebarGroupLabel,
8
- SidebarGroupContent,
9
- SidebarMenu,
10
- SidebarMenuItem,
11
- SidebarMenuButton,
12
- SidebarMenuSub,
13
- SidebarMenuSubItem,
14
- SidebarMenuSubButton,
15
- Badge,
16
- Input,
17
- Collapsible,
18
- CollapsibleTrigger,
19
- CollapsibleContent,
20
- } from '@object-ui/components';
21
- import { ChevronRight, Search } from 'lucide-react';
22
-
23
- export interface NavItem {
24
- title: string;
25
- href: string;
26
- icon?: React.ComponentType<{ className?: string }>;
27
- badge?: string | number;
28
- badgeVariant?: 'default' | 'destructive' | 'outline';
29
- children?: NavItem[];
30
- }
31
-
32
- export interface NavGroup {
33
- label: string;
34
- items: NavItem[];
35
- }
36
-
37
- export interface SidebarNavProps {
38
- items: NavItem[] | NavGroup[];
39
- title?: string;
40
- className?: string;
41
- collapsible?: "offcanvas" | "icon" | "none";
42
- searchEnabled?: boolean;
43
- searchPlaceholder?: string;
44
- }
45
-
46
- function isNavGroup(item: NavItem | NavGroup): item is NavGroup {
47
- return 'items' in item && !('href' in item);
48
- }
49
-
50
- function NavItemRenderer({ item, pathname }: { item: NavItem; pathname: string }) {
51
- if (item.children && item.children.length > 0) {
52
- return (
53
- <Collapsible asChild defaultOpen className="group/collapsible">
54
- <SidebarMenuItem>
55
- <CollapsibleTrigger asChild>
56
- <SidebarMenuButton tooltip={item.title}>
57
- {item.icon && <item.icon />}
58
- <span>{item.title}</span>
59
- {item.badge != null && (
60
- <Badge variant={item.badgeVariant || 'default'} className="ml-auto mr-1 h-5 min-w-5 px-1 text-xs">
61
- {item.badge}
62
- </Badge>
63
- )}
64
- <ChevronRight className="ml-auto h-4 w-4 transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
65
- </SidebarMenuButton>
66
- </CollapsibleTrigger>
67
- <CollapsibleContent>
68
- <SidebarMenuSub>
69
- {item.children.map((child) => (
70
- <SidebarMenuSubItem key={child.href}>
71
- <SidebarMenuSubButton asChild isActive={pathname === child.href}>
72
- <NavLink to={child.href}>
73
- {child.icon && <child.icon />}
74
- <span>{child.title}</span>
75
- {child.badge != null && (
76
- <Badge variant={child.badgeVariant || 'default'} className="ml-auto h-5 min-w-5 px-1 text-xs">
77
- {child.badge}
78
- </Badge>
79
- )}
80
- </NavLink>
81
- </SidebarMenuSubButton>
82
- </SidebarMenuSubItem>
83
- ))}
84
- </SidebarMenuSub>
85
- </CollapsibleContent>
86
- </SidebarMenuItem>
87
- </Collapsible>
88
- );
89
- }
90
-
91
- return (
92
- <SidebarMenuItem>
93
- <SidebarMenuButton asChild isActive={pathname === item.href} tooltip={item.title}>
94
- <NavLink to={item.href}>
95
- {item.icon && <item.icon />}
96
- <span>{item.title}</span>
97
- {item.badge != null && (
98
- <Badge variant={item.badgeVariant || 'default'} className="ml-auto h-5 min-w-5 px-1 text-xs">
99
- {item.badge}
100
- </Badge>
101
- )}
102
- </NavLink>
103
- </SidebarMenuButton>
104
- </SidebarMenuItem>
105
- );
106
- }
107
-
108
- export function SidebarNav({ items, title = "Application", className, collapsible = "icon", searchEnabled = false, searchPlaceholder = "Search..." }: SidebarNavProps) {
109
- const location = useLocation();
110
- const [search, setSearch] = React.useState('');
111
-
112
- const flatItems: Array<{ groupLabel?: string; items: NavItem[] }> = React.useMemo(() => {
113
- if (items.length === 0) return [];
114
- if (isNavGroup(items[0])) {
115
- return (items as NavGroup[]).map(g => ({ groupLabel: g.label, items: g.items }));
116
- }
117
- return [{ items: items as NavItem[] }];
118
- }, [items]);
119
-
120
- const filteredGroups = React.useMemo(() => {
121
- if (!search) return flatItems;
122
- const lowerSearch = search.toLowerCase();
123
- return flatItems.map(group => ({
124
- ...group,
125
- items: group.items.filter(item =>
126
- item.title.toLowerCase().includes(lowerSearch) ||
127
- item.children?.some(child => child.title.toLowerCase().includes(lowerSearch))
128
- ),
129
- })).filter(group => group.items.length > 0);
130
- }, [flatItems, search]);
131
-
132
- return (
133
- <Sidebar className={className} collapsible={collapsible}>
134
- <SidebarContent>
135
- {searchEnabled && (
136
- <div className="px-3 py-2">
137
- <div className="relative">
138
- <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
139
- <Input
140
- type="search"
141
- placeholder={searchPlaceholder}
142
- value={search}
143
- onChange={(e) => setSearch(e.target.value)}
144
- className="pl-8 h-9"
145
- />
146
- </div>
147
- </div>
148
- )}
149
- {filteredGroups.map((group, gIdx) => (
150
- <SidebarGroup key={group.groupLabel || gIdx}>
151
- <SidebarGroupLabel>{group.groupLabel || title}</SidebarGroupLabel>
152
- <SidebarGroupContent>
153
- <SidebarMenu>
154
- {group.items.map((item) => (
155
- <NavItemRenderer key={item.href} item={item} pathname={location.pathname} />
156
- ))}
157
- </SidebarMenu>
158
- </SidebarGroupContent>
159
- </SidebarGroup>
160
- ))}
161
- </SidebarContent>
162
- </Sidebar>
163
- );
164
- }