@allbluecn/admin-app 0.3.3 → 0.3.5

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 (109) hide show
  1. package/package.json +4 -3
  2. package/src/components/admin/admin-user-menu.tsx +4 -4
  3. package/src/components/admin/developer-log.tsx +7 -7
  4. package/src/components/ai-memory/memory-facts-panel.tsx +9 -9
  5. package/src/components/ai-prompts/preview-panel.tsx +1 -1
  6. package/src/components/ai-prompts/prompt-editor.tsx +8 -8
  7. package/src/components/ai-prompts/prompt-table.tsx +6 -6
  8. package/src/components/ai-prompts/section-editor.tsx +3 -3
  9. package/src/components/ai-prompts/variable-panel.tsx +2 -2
  10. package/src/components/ai-prompts/version-history.tsx +5 -5
  11. package/src/components/ai-provider/model-list-editor.tsx +2 -2
  12. package/src/components/ai-provider/provider-form.tsx +7 -7
  13. package/src/components/ai-provider/provider-table.tsx +6 -6
  14. package/src/components/auth/admin-login-form.tsx +6 -6
  15. package/src/components/auth/icon-input.tsx +2 -2
  16. package/src/components/auth/password-input.tsx +2 -2
  17. package/src/components/component/component-form-dialog.tsx +8 -8
  18. package/src/components/component/component-import-dialog.tsx +6 -6
  19. package/src/components/component/component-table.tsx +3 -3
  20. package/src/components/confirm-dialog.tsx +94 -0
  21. package/src/components/data-table/data-table-pagination.tsx +121 -0
  22. package/src/components/data-table/data-table.tsx +218 -0
  23. package/src/components/data-table/table-detail-layout.tsx +97 -0
  24. package/src/components/dependency/changelog-dialog.tsx +2 -2
  25. package/src/components/dependency/columns.tsx +3 -3
  26. package/src/components/dependency/dependency-table.tsx +7 -7
  27. package/src/components/dependency/edit-dialog.tsx +8 -8
  28. package/src/components/invitations/invitations-page.tsx +13 -13
  29. package/src/components/knowledge-public/bookmark-form-dialog.tsx +6 -6
  30. package/src/components/knowledge-public/compliance-form-dialog.tsx +6 -6
  31. package/src/components/knowledge-public/legal-form-dialog.tsx +7 -7
  32. package/src/components/layout/sidebar-02/app-sidebar.tsx +1 -1
  33. package/src/components/layout/sidebar-02/index.tsx +1 -1
  34. package/src/components/layout/sidebar-02/nav-main.tsx +2 -2
  35. package/src/components/layout/sidebar-02/nav-notifications.tsx +3 -3
  36. package/src/components/market/columns.tsx +4 -4
  37. package/src/components/market/market-app-form.tsx +6 -6
  38. package/src/components/market/market-app-list.tsx +4 -4
  39. package/src/components/monitoring/error-list.tsx +1 -1
  40. package/src/components/monitoring/metrics-stats.tsx +1 -1
  41. package/src/components/monitoring/request-log-table.tsx +1 -1
  42. package/src/components/notion-avatar.tsx +126 -0
  43. package/src/components/oauth-providers/provider-page.tsx +9 -9
  44. package/src/components/permissions/assign-dialog.tsx +5 -5
  45. package/src/components/permissions/create-role-dialog.tsx +6 -6
  46. package/src/components/ui/alert-dialog.tsx +185 -0
  47. package/src/components/ui/alert.tsx +75 -0
  48. package/src/components/ui/avatar.tsx +106 -0
  49. package/src/components/ui/badge.tsx +51 -0
  50. package/src/components/ui/button.tsx +57 -0
  51. package/src/components/ui/card.tsx +102 -0
  52. package/src/components/ui/checkbox.tsx +27 -0
  53. package/src/components/ui/collapsible.tsx +19 -0
  54. package/src/components/ui/dialog.tsx +155 -0
  55. package/src/components/ui/dropdown-menu.tsx +265 -0
  56. package/src/components/ui/field.tsx +236 -0
  57. package/src/components/ui/input-group.tsx +157 -0
  58. package/src/components/ui/input.tsx +19 -0
  59. package/src/components/ui/label.tsx +17 -0
  60. package/src/components/ui/pagination.tsx +130 -0
  61. package/src/components/ui/select.tsx +232 -0
  62. package/src/components/ui/separator.tsx +23 -0
  63. package/src/components/ui/sheet.tsx +134 -0
  64. package/src/components/ui/sidebar.tsx +721 -0
  65. package/src/components/ui/skeleton.tsx +13 -0
  66. package/src/components/ui/switch.tsx +30 -0
  67. package/src/components/ui/table.tsx +113 -0
  68. package/src/components/ui/textarea.tsx +17 -0
  69. package/src/components/ui/toast.tsx +308 -0
  70. package/src/components/ui/tooltip.tsx +64 -0
  71. package/src/components/user/columns.tsx +4 -4
  72. package/src/components/user/settings-activity-log.tsx +7 -7
  73. package/src/components/user/settings-profile/avatar-uploader.tsx +2 -2
  74. package/src/components/user/settings-profile/social-links.tsx +2 -2
  75. package/src/components/user/settings-profile.tsx +6 -6
  76. package/src/components/user/user-create-dialog.tsx +6 -6
  77. package/src/components/user/user-detail/error-boundary.tsx +1 -1
  78. package/src/components/user/user-detail/user-detail-dialogs.tsx +6 -6
  79. package/src/components/user/user-filters.tsx +3 -3
  80. package/src/components/user-avatar.tsx +82 -0
  81. package/src/hooks/use-mobile.ts +19 -0
  82. package/src/lib/datetime.ts +226 -0
  83. package/src/lib/sanitize.ts +18 -0
  84. package/src/providers/index.tsx +1 -1
  85. package/src/routes/__root.tsx +1 -1
  86. package/src/routes/_nav/audit/route.lazy.tsx +1 -1
  87. package/src/routes/_nav/compliance/route.lazy.tsx +3 -3
  88. package/src/routes/_nav/components/route.lazy.tsx +4 -4
  89. package/src/routes/_nav/knowledge-public/bookmarks/route.lazy.tsx +6 -6
  90. package/src/routes/_nav/knowledge-public/compliance/route.lazy.tsx +6 -6
  91. package/src/routes/_nav/knowledge-public/legal/route.lazy.tsx +6 -6
  92. package/src/routes/_nav/licenses/$licenseId/route.lazy.tsx +3 -3
  93. package/src/routes/_nav/licenses/new/route.lazy.tsx +5 -5
  94. package/src/routes/_nav/licenses/route.lazy.tsx +1 -1
  95. package/src/routes/_nav/monitoring/ai-usage/route.lazy.tsx +2 -2
  96. package/src/routes/_nav/monitoring/errors/route.lazy.tsx +1 -1
  97. package/src/routes/_nav/monitoring/health/route.lazy.tsx +3 -3
  98. package/src/routes/_nav/monitoring/logs/route.lazy.tsx +3 -3
  99. package/src/routes/_nav/monitoring/metrics/route.lazy.tsx +3 -3
  100. package/src/routes/_nav/monitoring/route.lazy.tsx +2 -2
  101. package/src/routes/_nav/monitoring/sentry/route.lazy.tsx +1 -1
  102. package/src/routes/_nav/permissions/route.lazy.tsx +4 -4
  103. package/src/routes/_nav/sso/route.lazy.tsx +2 -2
  104. package/src/routes/_nav/users/$userId/route.lazy.tsx +8 -8
  105. package/src/routes/_nav/users/-components/users-table.tsx +1 -1
  106. package/src/routes/_nav/users/route.lazy.tsx +1 -1
  107. package/src/routes/setup/route.lazy.tsx +2 -2
  108. package/src/server/dependency/helpers.ts +0 -1
  109. package/src/styles/globals.css +3 -3
@@ -25,18 +25,18 @@ import {
25
25
  DialogFooter,
26
26
  DialogHeader,
27
27
  DialogTitle,
28
- } from "@allbluecn/ui/dialog"
29
- import { Button } from "@allbluecn/ui/button"
30
- import { Input } from "@allbluecn/ui/input"
31
- import { Label } from "@allbluecn/ui/label"
28
+ } from "@/components/ui/dialog"
29
+ import { Button } from "@/components/ui/button"
30
+ import { Input } from "@/components/ui/input"
31
+ import { Label } from "@/components/ui/label"
32
32
  import {
33
33
  Select,
34
34
  SelectContent,
35
35
  SelectItem,
36
36
  SelectTrigger,
37
37
  SelectValue,
38
- } from "@allbluecn/ui/select"
39
- import { toast } from '@allbluecn/ui/toast'
38
+ } from "@/components/ui/select"
39
+ import { toast } from '@/components/ui/toast'
40
40
 
41
41
  interface MarketAppFormProps {
42
42
  open: boolean
@@ -17,15 +17,15 @@
17
17
 
18
18
  import { useCallback, useEffect, useState } from "react"
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { DataTable } from "@allbluecn/ui/data-table"
20
+ import { DataTable } from "@/components/data-table/data-table"
21
21
  import { getMarketColumns, MarketAppRow } from "./columns"
22
22
  import { MarketAppForm } from "./market-app-form"
23
23
  import { MarketAppFormData } from "@/lib/anyhub/types"
24
- import { Button } from "@allbluecn/ui/button"
24
+ import { Button } from "@/components/ui/button"
25
25
  import { PlusIcon } from "lucide-react"
26
- import { toast } from '@allbluecn/ui/toast'
26
+ import { toast } from '@/components/ui/toast'
27
27
  import { listMarketAppsFn, updateMarketAppFn, deleteMarketAppFn } from "@/server/serverFns/anyhub/market"
28
- import { ConfirmDialog } from "@allbluecn/ui/components/confirm-dialog"
28
+ import { ConfirmDialog } from "@/components/confirm-dialog"
29
29
 
30
30
  export function MarketAppList() {
31
31
  const [apps, setApps] = useState<MarketAppRow[]>([])
@@ -17,7 +17,7 @@
17
17
 
18
18
  import { useState } from 'react'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { DataTable, type AdminColumnDef } from '@allbluecn/ui/data-table'
20
+ import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
21
21
 
22
22
  function formatRelativeTime(dateStr: string): string {
23
23
  const now = new Date()
@@ -15,7 +15,7 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- import { DataTable, type AdminColumnDef } from '@allbluecn/ui/data-table'
18
+ import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
19
19
  import { m } from '@/paraglide/messages.js'
20
20
  import { describeMetric } from '@/lib/monitoring-descriptions'
21
21
  import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
@@ -17,7 +17,7 @@
17
17
 
18
18
  import type React from 'react'
19
19
  import { m } from '@/paraglide/messages.js'
20
- import { DataTable, type AdminColumnDef } from '@allbluecn/ui/data-table'
20
+ import { DataTable, type AdminColumnDef } from '@/components/data-table/data-table'
21
21
  import { describePath } from '@/lib/monitoring-descriptions'
22
22
  import { getBenchmark, getBenchmarkSource, compareDuration } from '@/lib/monitoring-benchmarks'
23
23
  import { ArrowDown, ArrowUp, Info } from 'lucide-react'
@@ -0,0 +1,126 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+
19
+ import { useEffect, useState } from "react"
20
+ import type { AvatarConfig, AvatarPart, ShapeType } from "@allbluecn/kernel"
21
+ import { sanitizeSvg } from "../lib/sanitize"
22
+
23
+ const noneParts: AvatarPart[] = ["glasses", "hair", "accessories", "details", "beard"]
24
+
25
+ const partOrder: AvatarPart[] = [
26
+ "face",
27
+ "nose",
28
+ "mouth",
29
+ "eyes",
30
+ "eyebrows",
31
+ "glasses",
32
+ "hair",
33
+ "accessories",
34
+ "details",
35
+ "beard",
36
+ ]
37
+
38
+ function getPreviewImagePath(part: AvatarPart, index: number): string {
39
+ return `/avatar/preview/${part}/${index}.svg`
40
+ }
41
+
42
+ const SVGFilter = `<defs>
43
+ <filter id="notion-avatar-filter" x="-20%" y="-20%" width="140%" height="140%" filterUnits="objectBoundingBox" primitiveUnits="userSpaceOnUse" color-interpolation-filters="linearRGB">
44
+ <feMorphology operator="dilate" radius="20" in="SourceAlpha" result="morphology"/>
45
+ <feFlood flood-color="#ffffff" flood-opacity="1" result="flood"/>
46
+ <feComposite in="flood" in2="morphology" operator="in" result="composite"/>
47
+ <feMerge result="merge">
48
+ <feMergeNode in="composite" result="mergeNode"/>
49
+ <feMergeNode in="SourceGraphic" result="mergeNode1"/>
50
+ </feMerge>
51
+ </filter>
52
+ </defs>`
53
+
54
+ function extractInnerSvg(svgText: string): string {
55
+ return svgText.replace(/<svg[^>]*>/i, "").replace(/<\/svg>/i, "").trim()
56
+ }
57
+
58
+ const shapeStyle: Record<ShapeType, string> = {
59
+ circle: "rounded-full",
60
+ rounded: "rounded-lg",
61
+ square: "rounded-none",
62
+ }
63
+
64
+ type NotionAvatarProps = {
65
+ config: AvatarConfig
66
+ bgColor?: string
67
+ shape?: ShapeType
68
+ className?: string
69
+ style?: React.CSSProperties
70
+ flip?: boolean
71
+ }
72
+
73
+ export function NotionAvatar({
74
+ config,
75
+ bgColor = "transparent",
76
+ shape = "circle",
77
+ className,
78
+ style,
79
+ flip = false,
80
+ }: NotionAvatarProps) {
81
+ const [svgContent, setSvgContent] = useState<string>("")
82
+
83
+ useEffect(() => {
84
+ async function buildAvatar() {
85
+ const groups = await Promise.all(
86
+ partOrder.map(async (part) => {
87
+ const index = config[part]
88
+ if (noneParts.includes(part) && index === 0) return ""
89
+ try {
90
+ const res = await fetch(getPreviewImagePath(part, index))
91
+ const svgText = await res.text()
92
+ const inner = extractInnerSvg(svgText)
93
+ return `\n<g id="avatar-${part}"${part === "face" ? ' fill="#ffffff"' : ""}>${inner}</g>`
94
+ } catch {
95
+ return ""
96
+ }
97
+ }),
98
+ )
99
+ const flipAttr = flip ? ' transform="scale(-1,1) translate(-1080, 0)"' : ""
100
+ const avatarSvg = `<svg viewBox="0 0 1080 1080" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%">${SVGFilter}<g id="avatar"${flipAttr}>${groups.join("")}</g></svg>`
101
+ setSvgContent(avatarSvg)
102
+ }
103
+ buildAvatar()
104
+ }, [config, flip])
105
+
106
+ if (!svgContent) {
107
+ return (
108
+ <div
109
+ className={`${shapeStyle[shape]} overflow-hidden bg-muted animate-pulse ${className ?? ""}`}
110
+ style={{ aspectRatio: "1 / 1", ...style }}
111
+ />
112
+ )
113
+ }
114
+
115
+ return (
116
+ <div
117
+ className={`${shapeStyle[shape]} ${className ?? ""}`}
118
+ style={{
119
+ backgroundColor: bgColor === "transparent" ? undefined : bgColor,
120
+ aspectRatio: "1 / 1",
121
+ ...style,
122
+ }}
123
+ dangerouslySetInnerHTML={{ __html: sanitizeSvg(svgContent) }}
124
+ />
125
+ )
126
+ }
@@ -19,17 +19,17 @@ import { useEffect, useState } from 'react'
19
19
  import type React from 'react'
20
20
  import { m } from '@/paraglide/messages.js'
21
21
  import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
22
- import { toast } from '@allbluecn/ui/toast'
22
+ import { toast } from '@/components/ui/toast'
23
23
  import { Eye, EyeOff, KeyRound, PlugZap, Save, Trash2 } from 'lucide-react'
24
24
  import { PageHeader } from '@/components/common/page-header'
25
- import { Button } from '@allbluecn/ui/button'
26
- import { Badge } from '@allbluecn/ui/badge'
27
- import { Card, CardContent } from '@allbluecn/ui/card'
28
- import { Input } from '@allbluecn/ui/input'
29
- import { Label } from '@allbluecn/ui/label'
30
- import { Switch } from '@allbluecn/ui/switch'
31
- import { Skeleton } from '@allbluecn/ui/skeleton'
32
- import { ConfirmDialog } from '@allbluecn/ui/components/confirm-dialog'
25
+ import { Button } from '@/components/ui/button'
26
+ import { Badge } from '@/components/ui/badge'
27
+ import { Card, CardContent } from '@/components/ui/card'
28
+ import { Input } from '@/components/ui/input'
29
+ import { Label } from '@/components/ui/label'
30
+ import { Switch } from '@/components/ui/switch'
31
+ import { Skeleton } from '@/components/ui/skeleton'
32
+ import { ConfirmDialog } from '@/components/confirm-dialog'
33
33
  import {
34
34
  deleteOAuthProviderFn,
35
35
  listOAuthProvidersFn,
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
2
2
  import type React from 'react'
3
3
  import { m } from '@/paraglide/messages.js'
4
4
  import { useServerFn } from '@tanstack/react-start'
5
- import { toast } from '@allbluecn/ui/toast'
5
+ import { toast } from '@/components/ui/toast'
6
6
  import {
7
7
  assignRoleFn,
8
8
  unassignRoleFn,
@@ -15,9 +15,9 @@ import {
15
15
  DialogFooter,
16
16
  DialogHeader,
17
17
  DialogTitle,
18
- } from '@allbluecn/ui/dialog'
19
- import { Button } from '@allbluecn/ui/button'
20
- import { Input } from '@allbluecn/ui/input'
18
+ } from '@/components/ui/dialog'
19
+ import { Button } from '@/components/ui/button'
20
+ import { Input } from '@/components/ui/input'
21
21
  import {
22
22
  Table,
23
23
  TableBody,
@@ -25,7 +25,7 @@ import {
25
25
  TableHead,
26
26
  TableHeader,
27
27
  TableRow,
28
- } from '@allbluecn/ui/table'
28
+ } from '@/components/ui/table'
29
29
  import type { AssignmentItem, RoleItem } from './types'
30
30
 
31
31
  export function AssignDialog({
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'
2
2
  import type React from 'react'
3
3
  import { m } from '@/paraglide/messages.js'
4
4
  import { useServerFn } from '@tanstack/react-start'
5
- import { toast } from '@allbluecn/ui/toast'
5
+ import { toast } from '@/components/ui/toast'
6
6
  import { createRoleFn } from '@/server/serverFns/permissions'
7
7
  import {
8
8
  Dialog,
@@ -11,17 +11,17 @@ import {
11
11
  DialogFooter,
12
12
  DialogHeader,
13
13
  DialogTitle,
14
- } from '@allbluecn/ui/dialog'
15
- import { Button } from '@allbluecn/ui/button'
16
- import { Input } from '@allbluecn/ui/input'
17
- import { Label } from '@allbluecn/ui/label'
14
+ } from '@/components/ui/dialog'
15
+ import { Button } from '@/components/ui/button'
16
+ import { Input } from '@/components/ui/input'
17
+ import { Label } from '@/components/ui/label'
18
18
  import {
19
19
  Select,
20
20
  SelectContent,
21
21
  SelectItem,
22
22
  SelectTrigger,
23
23
  SelectValue,
24
- } from '@allbluecn/ui/select'
24
+ } from '@/components/ui/select'
25
25
  import { ALL_ACTIONS, ALL_RESOURCES, LEVELS, type ResourcePermission } from './types'
26
26
 
27
27
  export function CreateRoleDialog({
@@ -0,0 +1,185 @@
1
+ import * as React from "react"
2
+ import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"
3
+ import { cn } from "cn"
4
+
5
+ import { Button } from "./button"
6
+
7
+ function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props) {
8
+ return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
9
+ }
10
+
11
+ function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props) {
12
+ return (
13
+ <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
14
+ )
15
+ }
16
+
17
+ function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props) {
18
+ return (
19
+ <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
20
+ )
21
+ }
22
+
23
+ function AlertDialogOverlay({
24
+ className,
25
+ ...props
26
+ }: AlertDialogPrimitive.Backdrop.Props) {
27
+ return (
28
+ <AlertDialogPrimitive.Backdrop
29
+ data-slot="alert-dialog-overlay"
30
+ className={cn(
31
+ "fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function AlertDialogContent({
40
+ className,
41
+ size = "default",
42
+ ...props
43
+ }: AlertDialogPrimitive.Popup.Props & {
44
+ size?: "default" | "sm"
45
+ }) {
46
+ return (
47
+ <AlertDialogPortal>
48
+ <AlertDialogOverlay />
49
+ <AlertDialogPrimitive.Popup
50
+ data-slot="alert-dialog-content"
51
+ data-size={size}
52
+ className={cn(
53
+ "group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-xl bg-popover p-6 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
54
+ className
55
+ )}
56
+ {...props}
57
+ />
58
+ </AlertDialogPortal>
59
+ )
60
+ }
61
+
62
+ function AlertDialogHeader({
63
+ className,
64
+ ...props
65
+ }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-dialog-header"
69
+ className={cn(
70
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
71
+ className
72
+ )}
73
+ {...props}
74
+ />
75
+ )
76
+ }
77
+
78
+ function AlertDialogFooter({
79
+ className,
80
+ ...props
81
+ }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ data-slot="alert-dialog-footer"
85
+ className={cn(
86
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
87
+ className
88
+ )}
89
+ {...props}
90
+ />
91
+ )
92
+ }
93
+
94
+ function AlertDialogMedia({
95
+ className,
96
+ ...props
97
+ }: React.ComponentProps<"div">) {
98
+ return (
99
+ <div
100
+ data-slot="alert-dialog-media"
101
+ className={cn(
102
+ "mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
103
+ className
104
+ )}
105
+ {...props}
106
+ />
107
+ )
108
+ }
109
+
110
+ function AlertDialogTitle({
111
+ className,
112
+ ...props
113
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
114
+ return (
115
+ <AlertDialogPrimitive.Title
116
+ data-slot="alert-dialog-title"
117
+ className={cn(
118
+ "font-heading text-lg font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ )
124
+ }
125
+
126
+ function AlertDialogDescription({
127
+ className,
128
+ ...props
129
+ }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
130
+ return (
131
+ <AlertDialogPrimitive.Description
132
+ data-slot="alert-dialog-description"
133
+ className={cn(
134
+ "text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ }
141
+
142
+ function AlertDialogAction({
143
+ className,
144
+ ...props
145
+ }: React.ComponentProps<typeof Button>) {
146
+ return (
147
+ <Button
148
+ data-slot="alert-dialog-action"
149
+ className={cn(className)}
150
+ {...props}
151
+ />
152
+ )
153
+ }
154
+
155
+ function AlertDialogCancel({
156
+ className,
157
+ variant = "outline",
158
+ size = "default",
159
+ ...props
160
+ }: AlertDialogPrimitive.Close.Props &
161
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
162
+ return (
163
+ <AlertDialogPrimitive.Close
164
+ data-slot="alert-dialog-cancel"
165
+ className={cn(className)}
166
+ render={<Button variant={variant} size={size} />}
167
+ {...props}
168
+ />
169
+ )
170
+ }
171
+
172
+ export {
173
+ AlertDialog,
174
+ AlertDialogAction,
175
+ AlertDialogCancel,
176
+ AlertDialogContent,
177
+ AlertDialogDescription,
178
+ AlertDialogFooter,
179
+ AlertDialogHeader,
180
+ AlertDialogMedia,
181
+ AlertDialogOverlay,
182
+ AlertDialogPortal,
183
+ AlertDialogTitle,
184
+ AlertDialogTrigger,
185
+ }
@@ -0,0 +1,75 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+ import { cn } from "cn"
4
+
5
+ const alertVariants = cva(
6
+ "group/alert relative grid w-full gap-0.5 rounded-lg border px-4 py-3 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "bg-card text-card-foreground",
11
+ destructive:
12
+ "bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ }
19
+ )
20
+
21
+ function Alert({
22
+ className,
23
+ variant,
24
+ ...props
25
+ }: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
26
+ return (
27
+ <div
28
+ data-slot="alert"
29
+ role="alert"
30
+ className={cn(alertVariants({ variant }), className)}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
37
+ return (
38
+ <div
39
+ data-slot="alert-title"
40
+ className={cn(
41
+ "font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function AlertDescription({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<"div">) {
53
+ return (
54
+ <div
55
+ data-slot="alert-description"
56
+ className={cn(
57
+ "text-sm text-balance text-muted-foreground md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
58
+ className
59
+ )}
60
+ {...props}
61
+ />
62
+ )
63
+ }
64
+
65
+ function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
66
+ return (
67
+ <div
68
+ data-slot="alert-action"
69
+ className={cn("absolute top-2.5 right-3", className)}
70
+ {...props}
71
+ />
72
+ )
73
+ }
74
+
75
+ export { Alert, AlertTitle, AlertDescription, AlertAction }
@@ -0,0 +1,106 @@
1
+ import * as React from "react"
2
+ import { Avatar as AvatarPrimitive } from "@base-ui/react/avatar"
3
+ import { cn } from "cn"
4
+
5
+ function Avatar({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: AvatarPrimitive.Root.Props & {
10
+ size?: "default" | "sm" | "lg"
11
+ }) {
12
+ return (
13
+ <AvatarPrimitive.Root
14
+ data-slot="avatar"
15
+ data-size={size}
16
+ className={cn(
17
+ "group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:border-border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten",
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ )
23
+ }
24
+
25
+ function AvatarImage({ className, ...props }: AvatarPrimitive.Image.Props) {
26
+ return (
27
+ <AvatarPrimitive.Image
28
+ data-slot="avatar-image"
29
+ className={cn(
30
+ "aspect-square size-full rounded-full object-cover",
31
+ className
32
+ )}
33
+ {...props}
34
+ />
35
+ )
36
+ }
37
+
38
+ function AvatarFallback({
39
+ className,
40
+ ...props
41
+ }: AvatarPrimitive.Fallback.Props) {
42
+ return (
43
+ <AvatarPrimitive.Fallback
44
+ data-slot="avatar-fallback"
45
+ className={cn(
46
+ "flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
55
+ return (
56
+ <span
57
+ data-slot="avatar-badge"
58
+ className={cn(
59
+ "absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background select-none",
60
+ "group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
61
+ "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
62
+ "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
71
+ return (
72
+ <div
73
+ data-slot="avatar-group"
74
+ className={cn(
75
+ "group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ )
81
+ }
82
+
83
+ function AvatarGroupCount({
84
+ className,
85
+ ...props
86
+ }: React.ComponentProps<"div">) {
87
+ return (
88
+ <div
89
+ data-slot="avatar-group-count"
90
+ className={cn(
91
+ "relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
92
+ className
93
+ )}
94
+ {...props}
95
+ />
96
+ )
97
+ }
98
+
99
+ export {
100
+ Avatar,
101
+ AvatarImage,
102
+ AvatarFallback,
103
+ AvatarGroup,
104
+ AvatarGroupCount,
105
+ AvatarBadge,
106
+ }