@windrun-huaiin/base-ui 3.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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +190 -0
  3. package/dist/base-ui.css +3 -0
  4. package/dist/components/index.d.mts +144 -0
  5. package/dist/components/index.d.ts +144 -0
  6. package/dist/components/index.js +1699 -0
  7. package/dist/components/index.js.map +1 -0
  8. package/dist/components/index.mjs +1741 -0
  9. package/dist/components/index.mjs.map +1 -0
  10. package/dist/index.d.mts +47 -0
  11. package/dist/index.d.ts +47 -0
  12. package/dist/index.js +6055 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/index.mjs +5842 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/lib/index.d.mts +24 -0
  17. package/dist/lib/index.d.ts +24 -0
  18. package/dist/lib/index.js +1324 -0
  19. package/dist/lib/index.js.map +1 -0
  20. package/dist/lib/index.mjs +1372 -0
  21. package/dist/lib/index.mjs.map +1 -0
  22. package/dist/ui/index.d.mts +754 -0
  23. package/dist/ui/index.d.ts +754 -0
  24. package/dist/ui/index.js +5796 -0
  25. package/dist/ui/index.js.map +1 -0
  26. package/dist/ui/index.mjs +5593 -0
  27. package/dist/ui/index.mjs.map +1 -0
  28. package/package.json +120 -0
  29. package/src/assets/bitcoin.tsx +23 -0
  30. package/src/assets/clerk.tsx +23 -0
  31. package/src/assets/css.tsx +21 -0
  32. package/src/assets/csv.tsx +35 -0
  33. package/src/assets/d8.tsx +25 -0
  34. package/src/assets/diff.tsx +23 -0
  35. package/src/assets/dpa.tsx +22 -0
  36. package/src/assets/github.tsx +23 -0
  37. package/src/assets/html.tsx +22 -0
  38. package/src/assets/http.tsx +23 -0
  39. package/src/assets/index.ts +61 -0
  40. package/src/assets/iterm.tsx +23 -0
  41. package/src/assets/java.tsx +23 -0
  42. package/src/assets/json.tsx +23 -0
  43. package/src/assets/last-updated.tsx +23 -0
  44. package/src/assets/log.tsx +28 -0
  45. package/src/assets/mac.tsx +23 -0
  46. package/src/assets/markdown.tsx +24 -0
  47. package/src/assets/mdx.tsx +98 -0
  48. package/src/assets/mermaid.tsx +24 -0
  49. package/src/assets/scheme.tsx +22 -0
  50. package/src/assets/snippets.tsx +23 -0
  51. package/src/assets/sql.tsx +31 -0
  52. package/src/assets/subp.tsx +22 -0
  53. package/src/assets/t3p.tsx +23 -0
  54. package/src/assets/test.tsx +23 -0
  55. package/src/assets/txt.tsx +23 -0
  56. package/src/assets/xml.tsx +23 -0
  57. package/src/assets/yaml.tsx +23 -0
  58. package/src/components/404-page.tsx +106 -0
  59. package/src/components/global-icon.tsx +193 -0
  60. package/src/components/go-to-top.tsx +43 -0
  61. package/src/components/index.ts +10 -0
  62. package/src/components/language-detector.tsx +175 -0
  63. package/src/components/language-switcher.tsx +77 -0
  64. package/src/components/script/google-analytics-script.tsx +56 -0
  65. package/src/components/script/microsoft-clarity-script.tsx +24 -0
  66. package/src/index.ts +4 -0
  67. package/src/lib/icon-context.tsx +57 -0
  68. package/src/lib/index.ts +3 -0
  69. package/src/lib/site-icon.tsx +46 -0
  70. package/src/lib/theme-util.ts +7 -0
  71. package/src/styles/base-ui.css +2 -0
  72. package/src/ui/accordion.tsx +58 -0
  73. package/src/ui/alert-dialog.tsx +141 -0
  74. package/src/ui/alert.tsx +59 -0
  75. package/src/ui/aspect-ratio.tsx +7 -0
  76. package/src/ui/avatar.tsx +50 -0
  77. package/src/ui/badge.tsx +36 -0
  78. package/src/ui/breadcrumb.tsx +115 -0
  79. package/src/ui/button.tsx +76 -0
  80. package/src/ui/calendar.tsx +66 -0
  81. package/src/ui/card.tsx +79 -0
  82. package/src/ui/carousel.tsx +262 -0
  83. package/src/ui/chart.tsx +365 -0
  84. package/src/ui/checkbox.tsx +30 -0
  85. package/src/ui/collapsible.tsx +11 -0
  86. package/src/ui/command.tsx +153 -0
  87. package/src/ui/context-menu.tsx +200 -0
  88. package/src/ui/dialog.tsx +122 -0
  89. package/src/ui/drawer.tsx +118 -0
  90. package/src/ui/dropdown-menu.tsx +200 -0
  91. package/src/ui/form.tsx +178 -0
  92. package/src/ui/hover-card.tsx +29 -0
  93. package/src/ui/index.ts +52 -0
  94. package/src/ui/input-otp.tsx +71 -0
  95. package/src/ui/input.tsx +22 -0
  96. package/src/ui/label.tsx +26 -0
  97. package/src/ui/language-button.tsx +43 -0
  98. package/src/ui/menubar.tsx +236 -0
  99. package/src/ui/navigation-menu.tsx +128 -0
  100. package/src/ui/pagination.tsx +117 -0
  101. package/src/ui/popover.tsx +31 -0
  102. package/src/ui/progress.tsx +28 -0
  103. package/src/ui/radio-group.tsx +44 -0
  104. package/src/ui/resizable.tsx +45 -0
  105. package/src/ui/scroll-area.tsx +48 -0
  106. package/src/ui/select.tsx +160 -0
  107. package/src/ui/separator.tsx +31 -0
  108. package/src/ui/sheet.tsx +140 -0
  109. package/src/ui/sidebar.tsx +763 -0
  110. package/src/ui/skeleton.tsx +15 -0
  111. package/src/ui/slider.tsx +28 -0
  112. package/src/ui/sonner.tsx +31 -0
  113. package/src/ui/switch.tsx +29 -0
  114. package/src/ui/table.tsx +117 -0
  115. package/src/ui/tabs.tsx +55 -0
  116. package/src/ui/textarea.tsx +22 -0
  117. package/src/ui/toast.tsx +129 -0
  118. package/src/ui/toaster.tsx +35 -0
  119. package/src/ui/toggle-group.tsx +61 -0
  120. package/src/ui/toggle.tsx +45 -0
  121. package/src/ui/tooltip.tsx +30 -0
  122. package/src/ui/use-mobile.tsx +19 -0
  123. package/src/ui/use-toast.ts +194 -0
@@ -0,0 +1,43 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+ import { globalLucideIcons as icons } from '@base-ui/components/global-icon';
5
+ export default function GoToTop() {
6
+ const [isVisible, setIsVisible] = useState(false);
7
+
8
+ // 监听滚动事件
9
+ useEffect(() => {
10
+ const toggleVisibility = () => {
11
+ if (window.scrollY > 300) {
12
+ setIsVisible(true);
13
+ } else {
14
+ setIsVisible(false);
15
+ }
16
+ };
17
+
18
+ window.addEventListener('scroll', toggleVisibility);
19
+ return () => window.removeEventListener('scroll', toggleVisibility);
20
+ }, []);
21
+
22
+ // 回到顶部
23
+ const scrollToTop = () => {
24
+ window.scrollTo({
25
+ top: 0,
26
+ behavior: 'smooth'
27
+ });
28
+ };
29
+
30
+ return (
31
+ <>
32
+ {isVisible && (
33
+ <button
34
+ onClick={scrollToTop}
35
+ className="fixed bottom-6 right-6 p-3 bg-neutral-800 text-neutral-100 hover:bg-neutral-700 dark:bg-neutral-300 dark:text-neutral-900 dark:hover:bg-neutral-400 rounded-full shadow-lg transition-all z-50"
36
+ aria-label="Go to top"
37
+ >
38
+ <icons.ArrowUp size={20} />
39
+ </button>
40
+ )}
41
+ </>
42
+ );
43
+ }
@@ -0,0 +1,10 @@
1
+ // Base Components
2
+ export * from './404-page';
3
+ export * from './global-icon';
4
+ export * from './go-to-top';
5
+ export * from './language-detector';
6
+ export * from './language-switcher';
7
+
8
+ // Script Components
9
+ export * from './script/google-analytics-script';
10
+ export * from './script/microsoft-clarity-script';
@@ -0,0 +1,175 @@
1
+ /**
2
+ * @license
3
+ * MIT License
4
+ * Copyright (c) 2025 D8ger
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+ 'use client'
10
+
11
+ import { globalLucideIcons as icons } from "@base-ui/components/global-icon"
12
+ import { useLocale, useTranslations } from 'next-intl'
13
+ import { useRouter } from 'next/navigation'
14
+ import { useEffect, useState } from 'react'
15
+
16
+ type I18nConfig = {
17
+ locales: readonly string[];
18
+ detector: {
19
+ storagePrefix: string;
20
+ storageKey: string;
21
+ autoCloseTimeout: number;
22
+ expirationDays: number;
23
+ };
24
+ };
25
+
26
+ interface LanguageDetectorProps {
27
+ i18nConfig: I18nConfig;
28
+ }
29
+
30
+ type Locale = string;
31
+
32
+ interface LanguagePreference {
33
+ locale: string;
34
+ status: 'accepted' | 'rejected';
35
+ timestamp: number;
36
+ }
37
+
38
+ export default function LanguageDetector({ i18nConfig }: LanguageDetectorProps) {
39
+ const [show, setShow] = useState(false)
40
+ const [detectedLocale, setDetectedLocale] = useState<Locale | null>(null)
41
+ const currentLocale = useLocale()
42
+ const router = useRouter()
43
+ const t = useTranslations('languageDetection')
44
+
45
+ // Get the storage key from the configuration
46
+ const LANGUAGE_PREFERENCE_KEY = `${i18nConfig.detector.storagePrefix}-${i18nConfig.detector.storageKey}`
47
+
48
+ useEffect(() => {
49
+ // Get the browser language
50
+ const browserLang = navigator.language.split('-')[0] as Locale
51
+
52
+ // Get the language preference from localStorage
53
+ const savedPreference = localStorage.getItem(LANGUAGE_PREFERENCE_KEY)
54
+ const preference: LanguagePreference | null = savedPreference
55
+ ? JSON.parse(savedPreference)
56
+ : null
57
+
58
+ // Check if the language detection box should be displayed
59
+ const shouldShowDetector = () => {
60
+ if (!preference) return true;
61
+
62
+ // If the stored language is the same as the current language, do not display the detection box
63
+ if (preference.locale === currentLocale) return false;
64
+
65
+ // If the user has previously rejected switching to this language, do not display the detection box
66
+ if (preference.status === 'rejected' && preference.locale === browserLang) return false;
67
+
68
+ // If the user has previously accepted switching to this language, do not display the detection box
69
+ if (preference.status === 'accepted' && preference.locale === currentLocale) return false;
70
+
71
+ // Use the expiration time from the configuration
72
+ const expirationMs = i18nConfig.detector.expirationDays * 24 * 60 * 60 * 1000;
73
+ if (Date.now() - preference.timestamp < expirationMs) return false;
74
+
75
+ return true;
76
+ }
77
+
78
+ // Check if the browser language is in the supported language list and needs to display the detection box
79
+ if ((i18nConfig.locales as string[]).includes(browserLang) &&
80
+ browserLang !== currentLocale &&
81
+ shouldShowDetector()) {
82
+ setDetectedLocale(browserLang)
83
+ setShow(true)
84
+
85
+ // Use the automatic closing time from the configuration
86
+ const timer = setTimeout(() => {
87
+ console.log('[LanguageDetector] Auto closing after timeout')
88
+ setShow(false)
89
+ // Save the rejected state when the automatic closing occurs
90
+ savePreference(browserLang, 'rejected')
91
+ }, i18nConfig.detector.autoCloseTimeout)
92
+
93
+ return () => clearTimeout(timer)
94
+ }
95
+ }, [currentLocale])
96
+
97
+ // Save the language preference to localStorage
98
+ const savePreference = (locale: string, status: 'accepted' | 'rejected') => {
99
+ const preference: LanguagePreference = {
100
+ locale,
101
+ status,
102
+ timestamp: Date.now()
103
+ }
104
+ localStorage.setItem(LANGUAGE_PREFERENCE_KEY, JSON.stringify(preference))
105
+ }
106
+
107
+ const handleLanguageChange = () => {
108
+ if (detectedLocale) {
109
+ // Save the accepted state
110
+ savePreference(detectedLocale, 'accepted')
111
+
112
+ // Get the current path
113
+ const pathname = window.location.pathname
114
+ // Replace the language part
115
+ const newPathname = pathname.replace(`/${currentLocale}`, `/${detectedLocale}`)
116
+ // Redirect to the new path
117
+ router.push(newPathname)
118
+ setShow(false)
119
+ }
120
+ }
121
+
122
+ const handleClose = () => {
123
+ if (detectedLocale) {
124
+ // Save the rejected state
125
+ savePreference(detectedLocale, 'rejected')
126
+ }
127
+ setShow(false)
128
+ }
129
+
130
+ if (!detectedLocale || !show) return null
131
+
132
+ return (
133
+ <div className="fixed top-16 right-4 z-40 w-[420px]">
134
+ <div className={`shadow-lg rounded-lg transition-all duration-300 ${show ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'}
135
+ bg-linear-to-r from-purple-100/95 via-white/95 to-purple-100/95 backdrop-blur-xs
136
+ animate-gradient-x`}>
137
+ <div className="relative px-6 py-4 overflow-hidden">
138
+ <div className="relative z-10 flex flex-col gap-3">
139
+ <div className="flex items-start justify-between gap-4">
140
+ <div className="flex flex-col gap-1.5">
141
+ <h3 className="text-lg font-semibold text-gray-900">
142
+ {t('title')}
143
+ </h3>
144
+ <p className="text-base text-gray-600">
145
+ {t('description')} <span className="text-purple-500 font-semibold">{detectedLocale === 'zh' ? '中文' : 'English'}</span>?
146
+ </p>
147
+ </div>
148
+ <button
149
+ onClick={handleClose}
150
+ className="text-gray-500 hover:text-gray-700"
151
+ >
152
+ <icons.X className="h-5 w-5" />
153
+ </button>
154
+ </div>
155
+ <div className="flex items-center gap-3">
156
+ <button
157
+ onClick={handleClose}
158
+ className="flex-1 px-4 py-2 text-base bg-gray-100 text-gray-600 rounded-md hover:bg-gray-200"
159
+ >
160
+ {t('close')}
161
+ </button>
162
+ <button
163
+ onClick={handleLanguageChange}
164
+ className="flex-1 px-4 py-2 text-base bg-purple-500 text-white rounded-md hover:bg-purple-600"
165
+ >
166
+ {t('changeAction')}
167
+ </button>
168
+ </div>
169
+ </div>
170
+ <div className="absolute inset-0 bg-linear-to-r from-transparent via-purple-200/30 to-transparent animate-shimmer" />
171
+ </div>
172
+ </div>
173
+ </div>
174
+ )
175
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license
3
+ * MIT License
4
+ * Copyright (c) 2025 D8ger
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE file in the root directory of this source tree.
8
+ */
9
+
10
+ 'use client'
11
+
12
+ import { usePathname, useRouter } from 'next/navigation'
13
+ import { useLocale } from 'next-intl'
14
+ import { globalLucideIcons as icons } from "@base-ui/components/global-icon"
15
+ import {
16
+ DropdownMenu,
17
+ DropdownMenuContent,
18
+ DropdownMenuItem,
19
+ DropdownMenuTrigger,
20
+ } from '@base-ui/ui/dropdown-menu'
21
+ import { LanguageButton } from '@base-ui/ui/language-button'
22
+
23
+ interface LanguageSwitcherProps {
24
+ locales: readonly string[];
25
+ localeLabels: Record<string, string>;
26
+ }
27
+
28
+ export default function LanguageSwitcher({ locales, localeLabels }: LanguageSwitcherProps) {
29
+ const locale = useLocale()
30
+ const router = useRouter()
31
+ const pathname = usePathname()
32
+
33
+ const handleLocaleChange = (newLocale: string) => {
34
+ const newPathname = pathname.replace(`/${locale}`, `/${newLocale}`)
35
+ router.push(newPathname)
36
+ }
37
+
38
+ return (
39
+ <DropdownMenu>
40
+ <DropdownMenuTrigger asChild>
41
+ <LanguageButton
42
+ variant="ghost"
43
+ size="icon"
44
+ className="bg-linear-to-r from-purple-400 to-pink-600 hover:from-purple-500 hover:to-pink-700 text-white transform hover:scale-110 transition-all duration-300"
45
+ >
46
+ <icons.Globe className="h-5 w-5" />
47
+ </LanguageButton>
48
+ </DropdownMenuTrigger>
49
+ <DropdownMenuContent
50
+ align="end"
51
+ sideOffset={5}
52
+ className="bg-white/90 dark:bg-gray-800/90 border-purple-100 dark:border-purple-800 w-[200px] p-2 backdrop-blur-xs translate-x-[50px]"
53
+ >
54
+ <div className="grid grid-cols-2 gap-1">
55
+ {locales.map((loc) => (
56
+ <DropdownMenuItem
57
+ key={loc}
58
+ className={`
59
+ px-2 py-2 text-sm cursor-pointer text-center justify-center
60
+ transition-all duration-300 ease-in-out
61
+ hover:scale-105 hover:shadow-md
62
+ rounded-md whitespace-nowrap
63
+ ${locale === loc
64
+ ? 'bg-linear-to-r from-purple-400 to-pink-600 text-white font-medium shadow-lg scale-105'
65
+ : 'hover:bg-linear-to-r hover:from-purple-400/10 hover:to-pink-600/10 hover:text-transparent hover:bg-clip-text'
66
+ }
67
+ `}
68
+ onClick={() => handleLocaleChange(loc)}
69
+ >
70
+ {localeLabels[loc]}
71
+ </DropdownMenuItem>
72
+ ))}
73
+ </div>
74
+ </DropdownMenuContent>
75
+ </DropdownMenu>
76
+ )
77
+ }
@@ -0,0 +1,56 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ "use client";
3
+
4
+ import Script from "next/script";
5
+
6
+ const googleAnalyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID!;
7
+
8
+ export function GoogleAnalyticsScript() {
9
+ // 只在生产环境中加载 Microsoft Clarity
10
+ if (process.env.NODE_ENV !== 'production') {
11
+ return null
12
+ }
13
+
14
+ return (
15
+ <>
16
+ <Script
17
+ strategy="afterInteractive"
18
+ src={`https://www.googletagmanager.com/gtag/js?id=${googleAnalyticsId}`}
19
+ />
20
+ <Script
21
+ id="google-analytics"
22
+ strategy="afterInteractive"
23
+ dangerouslySetInnerHTML={{
24
+ __html: `
25
+ window.dataLayer = window.dataLayer || [];
26
+ function gtag(){dataLayer.push(arguments);}
27
+ gtag('js', new Date());
28
+ gtag('config', '${googleAnalyticsId}');
29
+ `,
30
+ }}
31
+ />
32
+ </>
33
+ );
34
+ }
35
+
36
+ export function useGoogleAnalytics() {
37
+ const trackEvent = (event: string, data?: Record<string, unknown>) => {
38
+ if (typeof window === "undefined" || !window.gtag) {
39
+ return;
40
+ }
41
+
42
+ window.gtag("event", event, data);
43
+ };
44
+
45
+ return {
46
+ trackEvent,
47
+ };
48
+ }
49
+
50
+ // 为 window 添加 gtag 类型定义
51
+ declare global {
52
+ interface Window {
53
+ dataLayer: any[];
54
+ gtag: (...args: any[]) => void;
55
+ }
56
+ }
@@ -0,0 +1,24 @@
1
+ 'use client'
2
+
3
+ import Script from 'next/script'
4
+
5
+ const microsoftClarityId = process.env.NEXT_PUBLIC_MICROSOFT_CLARITY_ID!;
6
+
7
+ export default function MicrosoftClarityScript() {
8
+ // 只在生产环境中加载 Microsoft Clarity
9
+ if (process.env.NODE_ENV !== 'production') {
10
+ return null
11
+ }
12
+
13
+ return (
14
+ <Script id="microsoft-clarity" strategy="afterInteractive">
15
+ {`
16
+ (function(c,l,a,r,i,t,y){
17
+ c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
18
+ t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
19
+ y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
20
+ })(window, document, "clarity", "script", "${microsoftClarityId}");
21
+ `}
22
+ </Script>
23
+ )
24
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ // Re-export everything from sub-modules for convenience
2
+ export * from './ui';
3
+ export * from './components';
4
+ export * from './lib';
@@ -0,0 +1,57 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, type ComponentType, type ReactNode } from 'react';
4
+
5
+ export interface IconConfig {
6
+ siteIcon?: ComponentType | string;
7
+ }
8
+
9
+ // icon config context, directly store the config value
10
+ const IconConfigContext = createContext<IconConfig | null>(null);
11
+
12
+ interface IconConfigProviderProps {
13
+ config: IconConfig;
14
+ children: ReactNode;
15
+ }
16
+
17
+ /**
18
+ * IconConfigProvider - icon config provider based on React Context
19
+ * directly store the config value, without depending on module state
20
+ */
21
+ export function IconConfigProvider({ config, children }: IconConfigProviderProps) {
22
+ return (
23
+ <IconConfigContext.Provider value={config}>
24
+ {children}
25
+ </IconConfigContext.Provider>
26
+ );
27
+ }
28
+
29
+ /**
30
+ * internal hook: get icon config
31
+ * not exposed, only used by base-ui internal components
32
+ */
33
+ function useIconConfig(): IconConfig {
34
+ const config = useContext(IconConfigContext);
35
+
36
+ if (config === null) {
37
+ throw new Error(
38
+ '[SiteIcon] IconConfigProvider not found. Please wrap your app with <IconConfigProvider config={{ siteIcon: "YourIcon" }}>.'
39
+ );
40
+ }
41
+
42
+ return config;
43
+ }
44
+
45
+ /**
46
+ * internal hook: safe get specific icon config
47
+ * not exposed, only used by base-ui internal components
48
+ */
49
+ export function useIconConfigSafe(iconKey: keyof IconConfig): ComponentType | string | undefined {
50
+ try {
51
+ const config = useIconConfig();
52
+ return config[iconKey];
53
+ } catch {
54
+ // if there is no provider, return undefined, let the caller handle it
55
+ return undefined;
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ // Icon Configuration
2
+ export { IconConfigProvider, type IconConfig } from './icon-context';
3
+ export { SiteIcon } from './site-icon';
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { type LucideProps } from 'lucide-react';
4
+ import { cn } from '@lib/utils';
5
+ import { useIconConfigSafe } from '@base-ui/lib/icon-context';
6
+ import { globalLucideIcons } from '@base-ui/components/global-icon';
7
+ import { themeIconColor } from '@base-ui/lib/theme-util';
8
+
9
+ /**
10
+ * site icon component - client component
11
+ * based on React Context to get the config, solve the problem of cross-package module instance isolation
12
+ */
13
+ export function SiteIcon({
14
+ size = 24,
15
+ className,
16
+ ...props
17
+ }: Omit<LucideProps, 'children'>) {
18
+ const configuredIcon = useIconConfigSafe('siteIcon');
19
+
20
+ if (configuredIcon === undefined) {
21
+ throw new Error(
22
+ '[SiteIcon] Site icon is not configured. Please use <IconConfigProvider config={{ siteIcon: YourCustomIcon }}> or <IconConfigProvider config={{ siteIcon: "IconKeyName" }}> to set a custom site icon to avoid legal risks.'
23
+ );
24
+ }
25
+
26
+ // render the icon, pass in the config value and attributes
27
+ if (typeof configuredIcon === 'string') {
28
+ // string type: the key name of globalLucideIcons
29
+ if (configuredIcon === '') {
30
+ // empty string use default icon
31
+ const DefaultIcon = globalLucideIcons['Download' as keyof typeof globalLucideIcons];
32
+ return <DefaultIcon size={size} className={cn(themeIconColor, className)} {...props} />;
33
+ }
34
+ const IconComponent = globalLucideIcons[configuredIcon as keyof typeof globalLucideIcons];
35
+ if (!IconComponent) {
36
+ throw new Error(`[SiteIcon] Icon key "${configuredIcon}" not found in globalLucideIcons.`);
37
+ }
38
+ return <IconComponent size={size} className={cn(themeIconColor, className)} {...props} />;
39
+ } else {
40
+ // React component type: custom icon component
41
+ const CustomIcon = configuredIcon as React.ComponentType<LucideProps>;
42
+ const hasColorClass = className && /text-\w+/.test(className);
43
+ const finalClassName = hasColorClass ? className : cn(themeIconColor, className);
44
+ return <CustomIcon size={size} className={finalClassName} {...props} />;
45
+ }
46
+ }
@@ -0,0 +1,7 @@
1
+
2
+ // Attention: This icon color will be used in the entire project, and it depends on the ENV variable NEXT_PUBLIC_STYLE_ICON_COLOR
3
+ export const themeIconColor = process.env.NEXT_PUBLIC_STYLE_ICON_COLOR || "text-purple-500";
4
+
5
+ // Attention: This icon color will be used in the entire project, and it depends on the ENV variable NEXT_PUBLIC_STYLE_SVG_ICON_COLOR
6
+ export const themeSvgIconColor = process.env.NEXT_PUBLIC_STYLE_SVG_ICON_COLOR || "#AC62FD";
7
+ export const themeSvgIconSize = process.env.NEXT_PUBLIC_STYLE_SVG_ICON_SIZE || 18
@@ -0,0 +1,2 @@
1
+
2
+
@@ -0,0 +1,58 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { globalLucideIcons as icons } from "@base-ui/components/global-icon"
6
+
7
+ import { cn } from "@lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <icons.ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ ))
41
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42
+
43
+ const AccordionContent = React.forwardRef<
44
+ React.ElementRef<typeof AccordionPrimitive.Content>,
45
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46
+ >(({ className, children, ...props }, ref) => (
47
+ <AccordionPrimitive.Content
48
+ ref={ref}
49
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
50
+ {...props}
51
+ >
52
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
53
+ </AccordionPrimitive.Content>
54
+ ))
55
+
56
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
57
+
58
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }