@mostrom/app-shell 0.1.0
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/.claude/ralph-loop.local.md +9 -0
- package/README.md +172 -0
- package/bin/init.js +269 -0
- package/bun.lock +401 -0
- package/components.json +28 -0
- package/package.json +74 -0
- package/scripts/publish-npm.sh +202 -0
- package/src/AppShell.tsx +847 -0
- package/src/components/PageHeader.tsx +160 -0
- package/src/components/data-table/README.md +447 -0
- package/src/components/data-table/data-table-preferences.tsx +184 -0
- package/src/components/data-table/data-table-toolbar.tsx +118 -0
- package/src/components/data-table/data-table.tsx +37 -0
- package/src/components/data-table/index.ts +32 -0
- package/src/components/global-header/AllServicesButton.tsx +127 -0
- package/src/components/global-header/CategoriesButton.tsx +120 -0
- package/src/components/global-header/GlobalHeader.tsx +59 -0
- package/src/components/global-header/GlobalHeaderSearch.tsx +57 -0
- package/src/components/global-header/HeaderUtilities.tsx +243 -0
- package/src/components/global-header/ServicesMenu.tsx +246 -0
- package/src/components/layout/AppBreadcrumb.tsx +70 -0
- package/src/components/layout/AppFlashbar.tsx +95 -0
- package/src/components/layout/AppLayout.tsx +271 -0
- package/src/components/layout/AppNavigation.tsx +313 -0
- package/src/components/layout/AppSidebar.tsx +229 -0
- package/src/components/patterns/index.ts +14 -0
- package/src/components/patterns/p-alert-5.tsx +19 -0
- package/src/components/patterns/p-autocomplete-5.tsx +89 -0
- package/src/components/patterns/p-breadcrumb-1.tsx +28 -0
- package/src/components/patterns/p-button-42.tsx +37 -0
- package/src/components/patterns/p-button-51.tsx +14 -0
- package/src/components/patterns/p-button-6.tsx +5 -0
- package/src/components/patterns/p-calendar-1.tsx +18 -0
- package/src/components/patterns/p-card-1.tsx +33 -0
- package/src/components/patterns/p-card-2.tsx +26 -0
- package/src/components/patterns/p-card-5.tsx +31 -0
- package/src/components/patterns/p-collapsible-7.tsx +121 -0
- package/src/components/patterns/p-command-6.tsx +113 -0
- package/src/components/patterns/p-dialog-1.tsx +56 -0
- package/src/components/patterns/p-dropdown-menu-1.tsx +38 -0
- package/src/components/patterns/p-dropdown-menu-11.tsx +122 -0
- package/src/components/patterns/p-dropdown-menu-14.tsx +165 -0
- package/src/components/patterns/p-dropdown-menu-9.tsx +108 -0
- package/src/components/patterns/p-empty-2.tsx +34 -0
- package/src/components/patterns/p-file-upload-1.tsx +72 -0
- package/src/components/patterns/p-filters-1.tsx +666 -0
- package/src/components/patterns/p-frame-2.tsx +26 -0
- package/src/components/patterns/p-tabs-2.tsx +129 -0
- package/src/components/reui/alert.tsx +92 -0
- package/src/components/reui/autocomplete.tsx +343 -0
- package/src/components/reui/badge.tsx +87 -0
- package/src/components/reui/data-grid/data-grid-column-filter.tsx +165 -0
- package/src/components/reui/data-grid/data-grid-column-header.tsx +339 -0
- package/src/components/reui/data-grid/data-grid-column-visibility.tsx +55 -0
- package/src/components/reui/data-grid/data-grid-pagination.tsx +224 -0
- package/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx +260 -0
- package/src/components/reui/data-grid/data-grid-table-dnd.tsx +253 -0
- package/src/components/reui/data-grid/data-grid-table.tsx +639 -0
- package/src/components/reui/data-grid/data-grid.tsx +209 -0
- package/src/components/reui/date-selector.tsx +1330 -0
- package/src/components/reui/filters.tsx +1869 -0
- package/src/components/reui/frame.tsx +134 -0
- package/src/components/reui/index.ts +17 -0
- package/src/components/reui/timeline.tsx +219 -0
- package/src/components/search/Autocomplete.tsx +183 -0
- package/src/components/search/AutocompleteClient.tsx +293 -0
- package/src/components/search/GlobalSearch.tsx +187 -0
- package/src/components/section-drawer/deal-drawer-content.tsx +891 -0
- package/src/components/section-drawer/index.ts +19 -0
- package/src/components/section-drawer/section-drawer.css +665 -0
- package/src/components/section-drawer/section-drawer.tsx +467 -0
- package/src/components/sectioned-list-board/README.md +78 -0
- package/src/components/sectioned-list-board/board-card-content.tsx +340 -0
- package/src/components/sectioned-list-board/date-range-filter.tsx +249 -0
- package/src/components/sectioned-list-board/index.ts +19 -0
- package/src/components/sectioned-list-board/sectioned-list-board.css +564 -0
- package/src/components/sectioned-list-board/sectioned-list-board.tsx +731 -0
- package/src/components/sectioned-list-board/sortable-card.tsx +314 -0
- package/src/components/sectioned-list-board/sortable-section.tsx +319 -0
- package/src/components/sectioned-list-board/types.ts +216 -0
- package/src/components/sectioned-list-table/README.md +80 -0
- package/src/components/sectioned-list-table/index.ts +14 -0
- package/src/components/sectioned-list-table/sectioned-list-table.css +534 -0
- package/src/components/sectioned-list-table/sectioned-list-table.tsx +740 -0
- package/src/components/sectioned-list-table/sortable-column-header.tsx +120 -0
- package/src/components/sectioned-list-table/sortable-row.tsx +420 -0
- package/src/components/sectioned-list-table/sortable-section.tsx +251 -0
- package/src/components/sectioned-list-table/table-cell-content.tsx +129 -0
- package/src/components/sectioned-list-table/types.ts +120 -0
- package/src/components/sectioned-list-table/use-column-preferences.ts +103 -0
- package/src/components/ui/actions-dropdown.tsx +109 -0
- package/src/components/ui/assignee-selector.tsx +209 -0
- package/src/components/ui/avatar.tsx +107 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/calendar.tsx +220 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/chart.tsx +376 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +182 -0
- package/src/components/ui/context-menu.tsx +250 -0
- package/src/components/ui/create-button-group.tsx +128 -0
- package/src/components/ui/dialog.tsx +156 -0
- package/src/components/ui/drawer.tsx +133 -0
- package/src/components/ui/dropdown-menu.tsx +255 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +248 -0
- package/src/components/ui/form.tsx +165 -0
- package/src/components/ui/index.ts +37 -0
- package/src/components/ui/input-group.tsx +168 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/label.tsx +22 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/page-header.tsx +80 -0
- package/src/components/ui/popover.tsx +87 -0
- package/src/components/ui/scroll-area.tsx +56 -0
- package/src/components/ui/select.tsx +190 -0
- package/src/components/ui/separator.tsx +26 -0
- package/src/components/ui/sheet.tsx +141 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +38 -0
- package/src/components/ui/switch.tsx +33 -0
- package/src/components/ui/tabs.tsx +91 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +57 -0
- package/src/hooks/use-copy-to-clipboard.ts +37 -0
- package/src/hooks/use-file-upload.ts +415 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/index.ts +95 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +1859 -0
- package/src/urls.ts +83 -0
- package/src/vite.d.ts +22 -0
- package/src/vite.js +241 -0
- package/tsconfig.base.json +18 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ChevronLeft } from "lucide-react";
|
|
3
|
+
import type { NavigationItem, NavigationFollowEvent } from "./AppNavigation";
|
|
4
|
+
import {
|
|
5
|
+
Sidebar,
|
|
6
|
+
SidebarContent,
|
|
7
|
+
SidebarHeader,
|
|
8
|
+
SidebarMenu,
|
|
9
|
+
SidebarMenuItem,
|
|
10
|
+
SidebarMenuButton,
|
|
11
|
+
SidebarMenuSub,
|
|
12
|
+
SidebarMenuSubItem,
|
|
13
|
+
SidebarMenuSubButton,
|
|
14
|
+
SidebarSeparator,
|
|
15
|
+
SidebarGroup,
|
|
16
|
+
SidebarGroupLabel,
|
|
17
|
+
SidebarGroupContent,
|
|
18
|
+
useSidebar,
|
|
19
|
+
} from "../ui/sidebar";
|
|
20
|
+
import { cn } from "../../lib/utils";
|
|
21
|
+
|
|
22
|
+
export interface AppSidebarProps {
|
|
23
|
+
/** Service name displayed in the header */
|
|
24
|
+
serviceName: string;
|
|
25
|
+
/** Link destination when clicking the service name */
|
|
26
|
+
headerHref?: string;
|
|
27
|
+
/** Navigation items for the sidebar */
|
|
28
|
+
items?: ReadonlyArray<NavigationItem>;
|
|
29
|
+
/** Currently active navigation item href for highlighting */
|
|
30
|
+
activeHref?: string;
|
|
31
|
+
/** Callback when a navigation item is clicked */
|
|
32
|
+
onFollow?: (event: NavigationFollowEvent) => void;
|
|
33
|
+
/** Additional className for the sidebar */
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* AppSidebar - A sidebar wrapper that provides Cloudscape-compatible API
|
|
39
|
+
* using shadcn Sidebar components internally.
|
|
40
|
+
*/
|
|
41
|
+
export function AppSidebar({
|
|
42
|
+
serviceName,
|
|
43
|
+
headerHref = "#",
|
|
44
|
+
items = [],
|
|
45
|
+
activeHref,
|
|
46
|
+
onFollow,
|
|
47
|
+
className,
|
|
48
|
+
}: AppSidebarProps) {
|
|
49
|
+
const { toggleSidebar } = useSidebar();
|
|
50
|
+
|
|
51
|
+
const handleClick = (
|
|
52
|
+
event: React.MouseEvent,
|
|
53
|
+
item: { href?: string; external?: boolean }
|
|
54
|
+
) => {
|
|
55
|
+
if (onFollow) {
|
|
56
|
+
const followEvent: NavigationFollowEvent = {
|
|
57
|
+
detail: {
|
|
58
|
+
href: item.href ?? "",
|
|
59
|
+
external: item.external ?? false,
|
|
60
|
+
},
|
|
61
|
+
preventDefault: () => event.preventDefault(),
|
|
62
|
+
stopPropagation: () => event.stopPropagation(),
|
|
63
|
+
};
|
|
64
|
+
onFollow(followEvent);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const renderItem = (item: NavigationItem, index: number): React.ReactNode => {
|
|
69
|
+
if (item.type === "divider") {
|
|
70
|
+
return <SidebarSeparator key={`divider-${index}`} />;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (item.type === "section") {
|
|
74
|
+
return (
|
|
75
|
+
<SidebarGroup key={`section-${index}`}>
|
|
76
|
+
{item.text && <SidebarGroupLabel>{item.text}</SidebarGroupLabel>}
|
|
77
|
+
<SidebarGroupContent>
|
|
78
|
+
<SidebarMenu>
|
|
79
|
+
{item.items?.map((subItem, subIndex) =>
|
|
80
|
+
renderItem(subItem, subIndex)
|
|
81
|
+
)}
|
|
82
|
+
</SidebarMenu>
|
|
83
|
+
</SidebarGroupContent>
|
|
84
|
+
</SidebarGroup>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (item.type === "section-group") {
|
|
89
|
+
return (
|
|
90
|
+
<SidebarGroup key={`section-group-${index}`}>
|
|
91
|
+
{item.title && <SidebarGroupLabel>{item.title}</SidebarGroupLabel>}
|
|
92
|
+
<SidebarGroupContent>
|
|
93
|
+
<SidebarMenu>
|
|
94
|
+
{item.items?.map((subItem, subIndex) =>
|
|
95
|
+
renderItem(subItem, subIndex)
|
|
96
|
+
)}
|
|
97
|
+
</SidebarMenu>
|
|
98
|
+
</SidebarGroupContent>
|
|
99
|
+
</SidebarGroup>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (item.type === "link") {
|
|
104
|
+
const isActive = activeHref === item.href;
|
|
105
|
+
|
|
106
|
+
// Check if this link has nested items
|
|
107
|
+
if (item.items && item.items.length > 0) {
|
|
108
|
+
return (
|
|
109
|
+
<SidebarMenuItem key={`link-${index}`}>
|
|
110
|
+
<SidebarMenuButton
|
|
111
|
+
asChild
|
|
112
|
+
isActive={isActive}
|
|
113
|
+
tooltip={item.text}
|
|
114
|
+
>
|
|
115
|
+
<a
|
|
116
|
+
href={item.href}
|
|
117
|
+
onClick={(e) => handleClick(e, item)}
|
|
118
|
+
target={item.external ? "_blank" : undefined}
|
|
119
|
+
rel={item.external ? "noopener noreferrer" : undefined}
|
|
120
|
+
>
|
|
121
|
+
<span>{item.text}</span>
|
|
122
|
+
</a>
|
|
123
|
+
</SidebarMenuButton>
|
|
124
|
+
<SidebarMenuSub>
|
|
125
|
+
{item.items.map((subItem, subIndex) => {
|
|
126
|
+
if (subItem.type !== "link") return null;
|
|
127
|
+
const isSubActive = activeHref === subItem.href;
|
|
128
|
+
return (
|
|
129
|
+
<SidebarMenuSubItem key={`sublink-${subIndex}`}>
|
|
130
|
+
<SidebarMenuSubButton
|
|
131
|
+
asChild
|
|
132
|
+
isActive={isSubActive}
|
|
133
|
+
>
|
|
134
|
+
<a
|
|
135
|
+
href={subItem.href}
|
|
136
|
+
onClick={(e) => handleClick(e, subItem)}
|
|
137
|
+
target={subItem.external ? "_blank" : undefined}
|
|
138
|
+
rel={subItem.external ? "noopener noreferrer" : undefined}
|
|
139
|
+
>
|
|
140
|
+
<span>{subItem.text}</span>
|
|
141
|
+
</a>
|
|
142
|
+
</SidebarMenuSubButton>
|
|
143
|
+
</SidebarMenuSubItem>
|
|
144
|
+
);
|
|
145
|
+
})}
|
|
146
|
+
</SidebarMenuSub>
|
|
147
|
+
</SidebarMenuItem>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<SidebarMenuItem key={`link-${index}`}>
|
|
153
|
+
<SidebarMenuButton
|
|
154
|
+
asChild
|
|
155
|
+
isActive={isActive}
|
|
156
|
+
tooltip={item.text}
|
|
157
|
+
>
|
|
158
|
+
<a
|
|
159
|
+
href={item.href}
|
|
160
|
+
onClick={(e) => handleClick(e, item)}
|
|
161
|
+
target={item.external ? "_blank" : undefined}
|
|
162
|
+
rel={item.external ? "noopener noreferrer" : undefined}
|
|
163
|
+
>
|
|
164
|
+
<span>{item.text}</span>
|
|
165
|
+
</a>
|
|
166
|
+
</SidebarMenuButton>
|
|
167
|
+
</SidebarMenuItem>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (item.type === "expandable-link-group") {
|
|
172
|
+
return (
|
|
173
|
+
<SidebarGroup key={`expandable-${index}`}>
|
|
174
|
+
{item.text && <SidebarGroupLabel>{item.text}</SidebarGroupLabel>}
|
|
175
|
+
<SidebarGroupContent>
|
|
176
|
+
<SidebarMenu>
|
|
177
|
+
{item.items?.map((subItem, subIndex) =>
|
|
178
|
+
renderItem(subItem, subIndex)
|
|
179
|
+
)}
|
|
180
|
+
</SidebarMenu>
|
|
181
|
+
</SidebarGroupContent>
|
|
182
|
+
</SidebarGroup>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return null;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<Sidebar className={cn("app-sidebar", className)}>
|
|
191
|
+
<SidebarHeader>
|
|
192
|
+
<div className="flex items-center justify-between">
|
|
193
|
+
<SidebarMenu className="flex-1">
|
|
194
|
+
<SidebarMenuItem>
|
|
195
|
+
<SidebarMenuButton
|
|
196
|
+
asChild
|
|
197
|
+
size="lg"
|
|
198
|
+
className="font-semibold"
|
|
199
|
+
>
|
|
200
|
+
<a href={headerHref} onClick={(e) => handleClick(e, { href: headerHref })}>
|
|
201
|
+
<span>{serviceName}</span>
|
|
202
|
+
</a>
|
|
203
|
+
</SidebarMenuButton>
|
|
204
|
+
</SidebarMenuItem>
|
|
205
|
+
</SidebarMenu>
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
onClick={toggleSidebar}
|
|
209
|
+
className="text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex h-8 w-8 items-center justify-center rounded-md"
|
|
210
|
+
aria-label="Close sidebar"
|
|
211
|
+
>
|
|
212
|
+
<ChevronLeft className="h-5 w-5" />
|
|
213
|
+
</button>
|
|
214
|
+
</div>
|
|
215
|
+
</SidebarHeader>
|
|
216
|
+
<SidebarContent>
|
|
217
|
+
<SidebarGroup>
|
|
218
|
+
<SidebarGroupContent>
|
|
219
|
+
<SidebarMenu>
|
|
220
|
+
{items.map((item, index) => renderItem(item, index))}
|
|
221
|
+
</SidebarMenu>
|
|
222
|
+
</SidebarGroupContent>
|
|
223
|
+
</SidebarGroup>
|
|
224
|
+
</SidebarContent>
|
|
225
|
+
</Sidebar>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export { useSidebar, SidebarProvider, SidebarTrigger, SidebarInset } from "./ui/sidebar";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Pattern Components (reui.io)
|
|
2
|
+
// Each pattern exports as Pattern, so we rename them for unique exports
|
|
3
|
+
// Note: AutocompletePattern uses @base-ui/react which has CJS/SSR issues
|
|
4
|
+
// export { Pattern as AutocompletePattern } from "./p-autocomplete-5";
|
|
5
|
+
export { Pattern as BreadcrumbPattern } from "./p-breadcrumb-1";
|
|
6
|
+
export { Pattern as CopyButtonPattern } from "./p-button-42";
|
|
7
|
+
export { Pattern as CardDefaultPattern } from "./p-card-1";
|
|
8
|
+
export { Pattern as CardWithHeaderPattern } from "./p-card-2";
|
|
9
|
+
export { Pattern as CardWithLinkPattern } from "./p-card-5";
|
|
10
|
+
export { Pattern as CollapsiblePattern } from "./p-collapsible-7";
|
|
11
|
+
export { Pattern as DialogPattern } from "./p-dialog-1";
|
|
12
|
+
export { Pattern as EmptyStatePattern } from "./p-empty-2";
|
|
13
|
+
export { Pattern as FileUploadPattern } from "./p-file-upload-1";
|
|
14
|
+
export { Pattern as FiltersPattern } from "./p-filters-1";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Alert,
|
|
3
|
+
AlertDescription,
|
|
4
|
+
AlertTitle,
|
|
5
|
+
} from "@/components/reui/alert"
|
|
6
|
+
import { CircleAlertIcon } from "lucide-react"
|
|
7
|
+
|
|
8
|
+
export function Pattern() {
|
|
9
|
+
return (
|
|
10
|
+
<Alert variant="info">
|
|
11
|
+
<CircleAlertIcon
|
|
12
|
+
/>
|
|
13
|
+
<AlertTitle>Info! Something important</AlertTitle>
|
|
14
|
+
<AlertDescription>
|
|
15
|
+
This is an important message. Please read it carefully.
|
|
16
|
+
</AlertDescription>
|
|
17
|
+
</Alert>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import {
|
|
3
|
+
Autocomplete,
|
|
4
|
+
AutocompleteContent,
|
|
5
|
+
AutocompleteEmpty,
|
|
6
|
+
AutocompleteInput,
|
|
7
|
+
AutocompleteItem,
|
|
8
|
+
AutocompleteList,
|
|
9
|
+
} from "@/components/reui/autocomplete"
|
|
10
|
+
|
|
11
|
+
export function Pattern() {
|
|
12
|
+
const [value, setValue] = useState<string>("")
|
|
13
|
+
|
|
14
|
+
const filteredItems = items.filter((item) =>
|
|
15
|
+
item.value.toLowerCase().includes(value.toLowerCase())
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="w-full max-w-xs">
|
|
20
|
+
<Autocomplete
|
|
21
|
+
value={value}
|
|
22
|
+
onValueChange={setValue}
|
|
23
|
+
items={filteredItems}
|
|
24
|
+
itemToStringValue={(item: unknown) => (item as Item).value}
|
|
25
|
+
>
|
|
26
|
+
<AutocompleteInput placeholder="e.g. feature" showClear />
|
|
27
|
+
<AutocompleteContent>
|
|
28
|
+
<AutocompleteEmpty>No items found.</AutocompleteEmpty>
|
|
29
|
+
<AutocompleteList>
|
|
30
|
+
{(item) => (
|
|
31
|
+
<AutocompleteItem key={item.id} value={item}>
|
|
32
|
+
{item.value}
|
|
33
|
+
</AutocompleteItem>
|
|
34
|
+
)}
|
|
35
|
+
</AutocompleteList>
|
|
36
|
+
</AutocompleteContent>
|
|
37
|
+
</Autocomplete>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Item {
|
|
43
|
+
id: string
|
|
44
|
+
value: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const items: Item[] = [
|
|
48
|
+
{ id: "t1", value: "feature" },
|
|
49
|
+
{ id: "t2", value: "fix" },
|
|
50
|
+
{ id: "t3", value: "bug" },
|
|
51
|
+
{ id: "t4", value: "docs" },
|
|
52
|
+
{ id: "t5", value: "internal" },
|
|
53
|
+
{ id: "t6", value: "mobile" },
|
|
54
|
+
{ id: "c-accordion", value: "component: accordion" },
|
|
55
|
+
{ id: "c-alert-dialog", value: "component: alert dialog" },
|
|
56
|
+
{ id: "c-autocomplete", value: "component: autocomplete" },
|
|
57
|
+
{ id: "c-avatar", value: "component: avatar" },
|
|
58
|
+
{ id: "c-checkbox", value: "component: checkbox" },
|
|
59
|
+
{ id: "c-checkbox-group", value: "component: checkbox group" },
|
|
60
|
+
{ id: "c-collapsible", value: "component: collapsible" },
|
|
61
|
+
{ id: "c-combobox", value: "component: combobox" },
|
|
62
|
+
{ id: "c-context-menu", value: "component: context menu" },
|
|
63
|
+
{ id: "c-dialog", value: "component: dialog" },
|
|
64
|
+
{ id: "c-field", value: "component: field" },
|
|
65
|
+
{ id: "c-fieldset", value: "component: fieldset" },
|
|
66
|
+
{ id: "c-filterable-menu", value: "component: filterable menu" },
|
|
67
|
+
{ id: "c-form", value: "component: form" },
|
|
68
|
+
{ id: "c-input", value: "component: input" },
|
|
69
|
+
{ id: "c-menu", value: "component: menu" },
|
|
70
|
+
{ id: "c-menubar", value: "component: menubar" },
|
|
71
|
+
{ id: "c-meter", value: "component: meter" },
|
|
72
|
+
{ id: "c-navigation-menu", value: "component: navigation menu" },
|
|
73
|
+
{ id: "c-number-field", value: "component: number field" },
|
|
74
|
+
{ id: "c-popover", value: "component: popover" },
|
|
75
|
+
{ id: "c-preview-card", value: "component: preview card" },
|
|
76
|
+
{ id: "c-progress", value: "component: progress" },
|
|
77
|
+
{ id: "c-radio", value: "component: radio" },
|
|
78
|
+
{ id: "c-scroll-area", value: "component: scroll area" },
|
|
79
|
+
{ id: "c-select", value: "component: select" },
|
|
80
|
+
{ id: "c-separator", value: "component: separator" },
|
|
81
|
+
{ id: "c-slider", value: "component: slider" },
|
|
82
|
+
{ id: "c-switch", value: "component: switch" },
|
|
83
|
+
{ id: "c-tabs", value: "component: tabs" },
|
|
84
|
+
{ id: "c-toast", value: "component: toast" },
|
|
85
|
+
{ id: "c-toggle", value: "component: toggle" },
|
|
86
|
+
{ id: "c-toggle-group", value: "component: toggle group" },
|
|
87
|
+
{ id: "c-toolbar", value: "component: toolbar" },
|
|
88
|
+
{ id: "c-tooltip", value: "component: tooltip" },
|
|
89
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Breadcrumb,
|
|
3
|
+
BreadcrumbItem,
|
|
4
|
+
BreadcrumbLink,
|
|
5
|
+
BreadcrumbList,
|
|
6
|
+
BreadcrumbPage,
|
|
7
|
+
BreadcrumbSeparator,
|
|
8
|
+
} from "@/components/ui/breadcrumb"
|
|
9
|
+
|
|
10
|
+
export function Pattern() {
|
|
11
|
+
return (
|
|
12
|
+
<Breadcrumb>
|
|
13
|
+
<BreadcrumbList>
|
|
14
|
+
<BreadcrumbItem>
|
|
15
|
+
<BreadcrumbLink href="#">Home</BreadcrumbLink>
|
|
16
|
+
</BreadcrumbItem>
|
|
17
|
+
<BreadcrumbSeparator />
|
|
18
|
+
<BreadcrumbItem>
|
|
19
|
+
<BreadcrumbLink href="#">Components</BreadcrumbLink>
|
|
20
|
+
</BreadcrumbItem>
|
|
21
|
+
<BreadcrumbSeparator />
|
|
22
|
+
<BreadcrumbItem>
|
|
23
|
+
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
|
|
24
|
+
</BreadcrumbItem>
|
|
25
|
+
</BreadcrumbList>
|
|
26
|
+
</Breadcrumb>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
|
4
|
+
import { Button } from "@/components/ui/button"
|
|
5
|
+
import {
|
|
6
|
+
Tooltip,
|
|
7
|
+
TooltipContent,
|
|
8
|
+
TooltipProvider,
|
|
9
|
+
TooltipTrigger,
|
|
10
|
+
} from "@/components/ui/tooltip"
|
|
11
|
+
import { CheckIcon, CopyIcon } from "lucide-react"
|
|
12
|
+
|
|
13
|
+
export function Pattern() {
|
|
14
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 1500 })
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<TooltipProvider>
|
|
18
|
+
<Tooltip>
|
|
19
|
+
<TooltipTrigger asChild>
|
|
20
|
+
<Button
|
|
21
|
+
size="icon"
|
|
22
|
+
variant="outline"
|
|
23
|
+
aria-label={isCopied ? "Copied" : "Copy"}
|
|
24
|
+
onClick={() => copyToClipboard("https://reui.io")}
|
|
25
|
+
>
|
|
26
|
+
{isCopied ? (
|
|
27
|
+
<CheckIcon aria-hidden="true" />
|
|
28
|
+
) : (
|
|
29
|
+
<CopyIcon aria-hidden="true" />
|
|
30
|
+
)}
|
|
31
|
+
</Button>
|
|
32
|
+
</TooltipTrigger>
|
|
33
|
+
<TooltipContent>{isCopied ? "Copied" : "Copy link"}</TooltipContent>
|
|
34
|
+
</Tooltip>
|
|
35
|
+
</TooltipProvider>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import { PlusIcon } from "lucide-react"
|
|
3
|
+
|
|
4
|
+
export function Pattern() {
|
|
5
|
+
return (
|
|
6
|
+
<Button className="group/fab relative flex h-10 w-10 items-center overflow-hidden rounded-full px-3 transition-[width] duration-300 ease-in-out hover:w-32">
|
|
7
|
+
<PlusIcon aria-hidden="true" className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transition-transform duration-300 group-hover/fab:left-3 group-hover/fab:translate-x-0" />
|
|
8
|
+
|
|
9
|
+
<span className="ml-8 pr-2 whitespace-nowrap opacity-0 transition-opacity duration-300 group-hover/fab:opacity-100">
|
|
10
|
+
Create New
|
|
11
|
+
</span>
|
|
12
|
+
</Button>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
|
|
5
|
+
import { Calendar } from "@/components/ui/calendar"
|
|
6
|
+
import { Card, CardContent } from "@/components/ui/card"
|
|
7
|
+
|
|
8
|
+
export function Pattern() {
|
|
9
|
+
const [date, setDate] = useState<Date | undefined>(new Date())
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<Card className="p-0">
|
|
13
|
+
<CardContent className="p-0">
|
|
14
|
+
<Calendar mode="single" onSelect={setDate} selected={date} />
|
|
15
|
+
</CardContent>
|
|
16
|
+
</Card>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardDescription,
|
|
6
|
+
CardFooter,
|
|
7
|
+
CardHeader,
|
|
8
|
+
CardTitle,
|
|
9
|
+
} from "@/components/ui/card"
|
|
10
|
+
|
|
11
|
+
export function Pattern() {
|
|
12
|
+
return (
|
|
13
|
+
<Card className="w-full max-w-xs">
|
|
14
|
+
<CardHeader>
|
|
15
|
+
<CardTitle>Default Card</CardTitle>
|
|
16
|
+
<CardDescription>
|
|
17
|
+
This card uses the default size variant.
|
|
18
|
+
</CardDescription>
|
|
19
|
+
</CardHeader>
|
|
20
|
+
<CardContent>
|
|
21
|
+
<p>
|
|
22
|
+
The card component supports a size prop that defaults to
|
|
23
|
+
"default" for standard spacing and sizing.
|
|
24
|
+
</p>
|
|
25
|
+
</CardContent>
|
|
26
|
+
<CardFooter>
|
|
27
|
+
<Button variant="outline" className="w-full">
|
|
28
|
+
Action
|
|
29
|
+
</Button>
|
|
30
|
+
</CardFooter>
|
|
31
|
+
</Card>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardContent,
|
|
4
|
+
CardDescription,
|
|
5
|
+
CardHeader,
|
|
6
|
+
CardTitle,
|
|
7
|
+
} from "@/components/ui/card"
|
|
8
|
+
|
|
9
|
+
export function Pattern() {
|
|
10
|
+
return (
|
|
11
|
+
<Card className="w-full max-w-xs">
|
|
12
|
+
<CardHeader className="border-b">
|
|
13
|
+
<CardTitle>Header with Border</CardTitle>
|
|
14
|
+
<CardDescription>
|
|
15
|
+
This is a card with a header that has a bottom border.
|
|
16
|
+
</CardDescription>
|
|
17
|
+
</CardHeader>
|
|
18
|
+
<CardContent>
|
|
19
|
+
<p>
|
|
20
|
+
The header has a border-b class applied, creating a visual separation
|
|
21
|
+
between the header and content sections.
|
|
22
|
+
</p>
|
|
23
|
+
</CardContent>
|
|
24
|
+
</Card>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Button } from "@/components/ui/button"
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardContent,
|
|
5
|
+
CardFooter,
|
|
6
|
+
CardHeader,
|
|
7
|
+
CardTitle,
|
|
8
|
+
} from "@/components/ui/card"
|
|
9
|
+
import { ExternalLinkIcon } from "lucide-react"
|
|
10
|
+
|
|
11
|
+
export function Pattern() {
|
|
12
|
+
return (
|
|
13
|
+
<Card className="w-full max-w-xs gap-2 pt-5">
|
|
14
|
+
<CardHeader>
|
|
15
|
+
<CardTitle>Need a help in Claim?</CardTitle>
|
|
16
|
+
</CardHeader>
|
|
17
|
+
<CardContent className="mb-2">
|
|
18
|
+
<p>
|
|
19
|
+
Go to this step by step guideline process on how to certify for your
|
|
20
|
+
weekly benefits:
|
|
21
|
+
</p>
|
|
22
|
+
</CardContent>
|
|
23
|
+
<CardFooter className="py-2">
|
|
24
|
+
<Button variant="link" className="px-0">
|
|
25
|
+
See our guideline
|
|
26
|
+
<ExternalLinkIcon aria-hidden="true" />
|
|
27
|
+
</Button>
|
|
28
|
+
</CardFooter>
|
|
29
|
+
</Card>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
|
|
3
|
+
import {
|
|
4
|
+
Frame,
|
|
5
|
+
FrameHeader,
|
|
6
|
+
FramePanel,
|
|
7
|
+
FrameTitle,
|
|
8
|
+
} from "@/components/reui/frame"
|
|
9
|
+
|
|
10
|
+
import { Button } from "@/components/ui/button"
|
|
11
|
+
import {
|
|
12
|
+
Collapsible,
|
|
13
|
+
CollapsibleContent,
|
|
14
|
+
CollapsibleTrigger,
|
|
15
|
+
} from "@/components/ui/collapsible"
|
|
16
|
+
import {
|
|
17
|
+
DropdownMenu,
|
|
18
|
+
DropdownMenuContent,
|
|
19
|
+
DropdownMenuItem,
|
|
20
|
+
DropdownMenuTrigger,
|
|
21
|
+
} from "@/components/ui/dropdown-menu"
|
|
22
|
+
import { ChevronRightIcon, PlusIcon, LockIcon, MoreHorizontalIcon, CheckIcon, CopyIcon, TrashIcon } from "lucide-react"
|
|
23
|
+
|
|
24
|
+
const initialKeys = [
|
|
25
|
+
{ id: "1", name: "Production", key: "AUDO230454*242SDIFPPL" },
|
|
26
|
+
{ id: "2", name: "Development", key: "DUILO30454*242SDIFUIP" },
|
|
27
|
+
{ id: "3", name: "Staging", key: "IPPODAS230454*242SDI" },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export function Pattern() {
|
|
31
|
+
const [keys, setKeys] = useState(initialKeys)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="h-54 w-full max-w-xs">
|
|
35
|
+
<Frame stacked dense spacing="sm">
|
|
36
|
+
<Collapsible defaultOpen>
|
|
37
|
+
<CollapsibleTrigger className="flex w-full">
|
|
38
|
+
<FrameHeader className="flex grow flex-row items-center justify-between gap-1.5 py-1!">
|
|
39
|
+
<ChevronRightIcon aria-hidden="true" className="text-muted-foreground size-4 transition-transform in-data-[state=open]:rotate-90" />
|
|
40
|
+
<FrameTitle className="text-sm font-medium">API Keys</FrameTitle>
|
|
41
|
+
<Button
|
|
42
|
+
variant="ghost"
|
|
43
|
+
size="icon-sm"
|
|
44
|
+
className="hover:border-border ml-auto"
|
|
45
|
+
>
|
|
46
|
+
<PlusIcon aria-hidden="true" />
|
|
47
|
+
</Button>
|
|
48
|
+
</FrameHeader>
|
|
49
|
+
</CollapsibleTrigger>
|
|
50
|
+
<CollapsibleContent>
|
|
51
|
+
<FramePanel>
|
|
52
|
+
<div className="flex flex-col gap-2.5">
|
|
53
|
+
{keys.map((item) => (
|
|
54
|
+
<ApiKeyItem
|
|
55
|
+
key={item.id}
|
|
56
|
+
item={item}
|
|
57
|
+
onDelete={(id) =>
|
|
58
|
+
setKeys((k) => k.filter((i) => i.id !== id))
|
|
59
|
+
}
|
|
60
|
+
/>
|
|
61
|
+
))}
|
|
62
|
+
</div>
|
|
63
|
+
</FramePanel>
|
|
64
|
+
</CollapsibleContent>
|
|
65
|
+
</Collapsible>
|
|
66
|
+
</Frame>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ApiKeyItem({
|
|
72
|
+
item,
|
|
73
|
+
onDelete,
|
|
74
|
+
}: {
|
|
75
|
+
item: (typeof initialKeys)[0]
|
|
76
|
+
onDelete: (id: string) => void
|
|
77
|
+
}) {
|
|
78
|
+
const { isCopied, copyToClipboard } = useCopyToClipboard()
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="flex items-center justify-between gap-3.5">
|
|
82
|
+
<div className="flex items-center gap-3">
|
|
83
|
+
<div className="flex items-center gap-1.5">
|
|
84
|
+
<div className="bg-muted border-border/60 flex size-5.5 shrink-0 items-center justify-center rounded-sm border-2">
|
|
85
|
+
<LockIcon aria-hidden="true" className="size-3.25 text-emerald-600" />
|
|
86
|
+
</div>
|
|
87
|
+
<div className="w-10 truncate text-xs">{item.name}</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div className="bg-muted w-40 truncate rounded-md px-2 py-1 text-xs">
|
|
90
|
+
{item.key}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
<div className="flex shrink-0 items-center">
|
|
94
|
+
<DropdownMenu>
|
|
95
|
+
<DropdownMenuTrigger asChild>
|
|
96
|
+
<Button variant="ghost" size="icon-xs">
|
|
97
|
+
<MoreHorizontalIcon aria-hidden="true" />
|
|
98
|
+
</Button>
|
|
99
|
+
</DropdownMenuTrigger>
|
|
100
|
+
<DropdownMenuContent align="start" className="min-w-32">
|
|
101
|
+
<DropdownMenuItem onClick={() => copyToClipboard(item.key)}>
|
|
102
|
+
{isCopied ? (
|
|
103
|
+
<CheckIcon aria-hidden="true" className="text-green-500" />
|
|
104
|
+
) : (
|
|
105
|
+
<CopyIcon aria-hidden="true" />
|
|
106
|
+
)}
|
|
107
|
+
<span>{isCopied ? "Copied" : "Copy key"}</span>
|
|
108
|
+
</DropdownMenuItem>
|
|
109
|
+
<DropdownMenuItem
|
|
110
|
+
variant="destructive"
|
|
111
|
+
onClick={() => onDelete(item.id)}
|
|
112
|
+
>
|
|
113
|
+
<TrashIcon aria-hidden="true" />
|
|
114
|
+
<span>Delete</span>
|
|
115
|
+
</DropdownMenuItem>
|
|
116
|
+
</DropdownMenuContent>
|
|
117
|
+
</DropdownMenu>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
}
|