@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.
Files changed (142) hide show
  1. package/.claude/ralph-loop.local.md +9 -0
  2. package/README.md +172 -0
  3. package/bin/init.js +269 -0
  4. package/bun.lock +401 -0
  5. package/components.json +28 -0
  6. package/package.json +74 -0
  7. package/scripts/publish-npm.sh +202 -0
  8. package/src/AppShell.tsx +847 -0
  9. package/src/components/PageHeader.tsx +160 -0
  10. package/src/components/data-table/README.md +447 -0
  11. package/src/components/data-table/data-table-preferences.tsx +184 -0
  12. package/src/components/data-table/data-table-toolbar.tsx +118 -0
  13. package/src/components/data-table/data-table.tsx +37 -0
  14. package/src/components/data-table/index.ts +32 -0
  15. package/src/components/global-header/AllServicesButton.tsx +127 -0
  16. package/src/components/global-header/CategoriesButton.tsx +120 -0
  17. package/src/components/global-header/GlobalHeader.tsx +59 -0
  18. package/src/components/global-header/GlobalHeaderSearch.tsx +57 -0
  19. package/src/components/global-header/HeaderUtilities.tsx +243 -0
  20. package/src/components/global-header/ServicesMenu.tsx +246 -0
  21. package/src/components/layout/AppBreadcrumb.tsx +70 -0
  22. package/src/components/layout/AppFlashbar.tsx +95 -0
  23. package/src/components/layout/AppLayout.tsx +271 -0
  24. package/src/components/layout/AppNavigation.tsx +313 -0
  25. package/src/components/layout/AppSidebar.tsx +229 -0
  26. package/src/components/patterns/index.ts +14 -0
  27. package/src/components/patterns/p-alert-5.tsx +19 -0
  28. package/src/components/patterns/p-autocomplete-5.tsx +89 -0
  29. package/src/components/patterns/p-breadcrumb-1.tsx +28 -0
  30. package/src/components/patterns/p-button-42.tsx +37 -0
  31. package/src/components/patterns/p-button-51.tsx +14 -0
  32. package/src/components/patterns/p-button-6.tsx +5 -0
  33. package/src/components/patterns/p-calendar-1.tsx +18 -0
  34. package/src/components/patterns/p-card-1.tsx +33 -0
  35. package/src/components/patterns/p-card-2.tsx +26 -0
  36. package/src/components/patterns/p-card-5.tsx +31 -0
  37. package/src/components/patterns/p-collapsible-7.tsx +121 -0
  38. package/src/components/patterns/p-command-6.tsx +113 -0
  39. package/src/components/patterns/p-dialog-1.tsx +56 -0
  40. package/src/components/patterns/p-dropdown-menu-1.tsx +38 -0
  41. package/src/components/patterns/p-dropdown-menu-11.tsx +122 -0
  42. package/src/components/patterns/p-dropdown-menu-14.tsx +165 -0
  43. package/src/components/patterns/p-dropdown-menu-9.tsx +108 -0
  44. package/src/components/patterns/p-empty-2.tsx +34 -0
  45. package/src/components/patterns/p-file-upload-1.tsx +72 -0
  46. package/src/components/patterns/p-filters-1.tsx +666 -0
  47. package/src/components/patterns/p-frame-2.tsx +26 -0
  48. package/src/components/patterns/p-tabs-2.tsx +129 -0
  49. package/src/components/reui/alert.tsx +92 -0
  50. package/src/components/reui/autocomplete.tsx +343 -0
  51. package/src/components/reui/badge.tsx +87 -0
  52. package/src/components/reui/data-grid/data-grid-column-filter.tsx +165 -0
  53. package/src/components/reui/data-grid/data-grid-column-header.tsx +339 -0
  54. package/src/components/reui/data-grid/data-grid-column-visibility.tsx +55 -0
  55. package/src/components/reui/data-grid/data-grid-pagination.tsx +224 -0
  56. package/src/components/reui/data-grid/data-grid-table-dnd-rows.tsx +260 -0
  57. package/src/components/reui/data-grid/data-grid-table-dnd.tsx +253 -0
  58. package/src/components/reui/data-grid/data-grid-table.tsx +639 -0
  59. package/src/components/reui/data-grid/data-grid.tsx +209 -0
  60. package/src/components/reui/date-selector.tsx +1330 -0
  61. package/src/components/reui/filters.tsx +1869 -0
  62. package/src/components/reui/frame.tsx +134 -0
  63. package/src/components/reui/index.ts +17 -0
  64. package/src/components/reui/timeline.tsx +219 -0
  65. package/src/components/search/Autocomplete.tsx +183 -0
  66. package/src/components/search/AutocompleteClient.tsx +293 -0
  67. package/src/components/search/GlobalSearch.tsx +187 -0
  68. package/src/components/section-drawer/deal-drawer-content.tsx +891 -0
  69. package/src/components/section-drawer/index.ts +19 -0
  70. package/src/components/section-drawer/section-drawer.css +665 -0
  71. package/src/components/section-drawer/section-drawer.tsx +467 -0
  72. package/src/components/sectioned-list-board/README.md +78 -0
  73. package/src/components/sectioned-list-board/board-card-content.tsx +340 -0
  74. package/src/components/sectioned-list-board/date-range-filter.tsx +249 -0
  75. package/src/components/sectioned-list-board/index.ts +19 -0
  76. package/src/components/sectioned-list-board/sectioned-list-board.css +564 -0
  77. package/src/components/sectioned-list-board/sectioned-list-board.tsx +731 -0
  78. package/src/components/sectioned-list-board/sortable-card.tsx +314 -0
  79. package/src/components/sectioned-list-board/sortable-section.tsx +319 -0
  80. package/src/components/sectioned-list-board/types.ts +216 -0
  81. package/src/components/sectioned-list-table/README.md +80 -0
  82. package/src/components/sectioned-list-table/index.ts +14 -0
  83. package/src/components/sectioned-list-table/sectioned-list-table.css +534 -0
  84. package/src/components/sectioned-list-table/sectioned-list-table.tsx +740 -0
  85. package/src/components/sectioned-list-table/sortable-column-header.tsx +120 -0
  86. package/src/components/sectioned-list-table/sortable-row.tsx +420 -0
  87. package/src/components/sectioned-list-table/sortable-section.tsx +251 -0
  88. package/src/components/sectioned-list-table/table-cell-content.tsx +129 -0
  89. package/src/components/sectioned-list-table/types.ts +120 -0
  90. package/src/components/sectioned-list-table/use-column-preferences.ts +103 -0
  91. package/src/components/ui/actions-dropdown.tsx +109 -0
  92. package/src/components/ui/assignee-selector.tsx +209 -0
  93. package/src/components/ui/avatar.tsx +107 -0
  94. package/src/components/ui/breadcrumb.tsx +109 -0
  95. package/src/components/ui/button-group.tsx +83 -0
  96. package/src/components/ui/button.tsx +64 -0
  97. package/src/components/ui/calendar.tsx +220 -0
  98. package/src/components/ui/card.tsx +92 -0
  99. package/src/components/ui/chart.tsx +376 -0
  100. package/src/components/ui/checkbox.tsx +30 -0
  101. package/src/components/ui/collapsible.tsx +33 -0
  102. package/src/components/ui/command.tsx +182 -0
  103. package/src/components/ui/context-menu.tsx +250 -0
  104. package/src/components/ui/create-button-group.tsx +128 -0
  105. package/src/components/ui/dialog.tsx +156 -0
  106. package/src/components/ui/drawer.tsx +133 -0
  107. package/src/components/ui/dropdown-menu.tsx +255 -0
  108. package/src/components/ui/empty.tsx +104 -0
  109. package/src/components/ui/field.tsx +248 -0
  110. package/src/components/ui/form.tsx +165 -0
  111. package/src/components/ui/index.ts +37 -0
  112. package/src/components/ui/input-group.tsx +168 -0
  113. package/src/components/ui/input.tsx +21 -0
  114. package/src/components/ui/kbd.tsx +28 -0
  115. package/src/components/ui/label.tsx +22 -0
  116. package/src/components/ui/navigation-menu.tsx +168 -0
  117. package/src/components/ui/page-header.tsx +80 -0
  118. package/src/components/ui/popover.tsx +87 -0
  119. package/src/components/ui/scroll-area.tsx +56 -0
  120. package/src/components/ui/select.tsx +190 -0
  121. package/src/components/ui/separator.tsx +26 -0
  122. package/src/components/ui/sheet.tsx +141 -0
  123. package/src/components/ui/sidebar.tsx +726 -0
  124. package/src/components/ui/skeleton.tsx +13 -0
  125. package/src/components/ui/sonner.tsx +38 -0
  126. package/src/components/ui/switch.tsx +33 -0
  127. package/src/components/ui/tabs.tsx +91 -0
  128. package/src/components/ui/textarea.tsx +18 -0
  129. package/src/components/ui/toggle-group.tsx +83 -0
  130. package/src/components/ui/toggle.tsx +45 -0
  131. package/src/components/ui/tooltip.tsx +57 -0
  132. package/src/hooks/use-copy-to-clipboard.ts +37 -0
  133. package/src/hooks/use-file-upload.ts +415 -0
  134. package/src/hooks/use-mobile.ts +19 -0
  135. package/src/index.ts +95 -0
  136. package/src/lib/utils.ts +6 -0
  137. package/src/styles.css +1859 -0
  138. package/src/urls.ts +83 -0
  139. package/src/vite.d.ts +22 -0
  140. package/src/vite.js +241 -0
  141. package/tsconfig.base.json +18 -0
  142. 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,5 @@
1
+ import { Button } from "@/components/ui/button"
2
+
3
+ export function Pattern() {
4
+ return <Button variant="link">Link</Button>
5
+ }
@@ -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
+ &quot;default&quot; 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
+ }