@djangocfg/ui-nextjs 1.4.45

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/package.json +110 -0
  4. package/src/animations/AnimatedBackground.tsx +645 -0
  5. package/src/animations/index.ts +2 -0
  6. package/src/blocks/ArticleCard.tsx +94 -0
  7. package/src/blocks/ArticleList.tsx +95 -0
  8. package/src/blocks/CTASection.tsx +136 -0
  9. package/src/blocks/FeatureSection.tsx +104 -0
  10. package/src/blocks/Hero.tsx +102 -0
  11. package/src/blocks/NewsletterSection.tsx +119 -0
  12. package/src/blocks/StatsSection.tsx +103 -0
  13. package/src/blocks/SuperHero.tsx +328 -0
  14. package/src/blocks/TestimonialSection.tsx +122 -0
  15. package/src/blocks/index.ts +9 -0
  16. package/src/components/README.md +2018 -0
  17. package/src/components/breadcrumb-navigation.tsx +127 -0
  18. package/src/components/breadcrumb.tsx +132 -0
  19. package/src/components/button-download.tsx +275 -0
  20. package/src/components/dropdown-menu.tsx +219 -0
  21. package/src/components/index.ts +86 -0
  22. package/src/components/markdown/MarkdownMessage.tsx +338 -0
  23. package/src/components/markdown/index.ts +5 -0
  24. package/src/components/menubar.tsx +274 -0
  25. package/src/components/multi-select-pro/async.tsx +608 -0
  26. package/src/components/multi-select-pro/helpers.tsx +84 -0
  27. package/src/components/multi-select-pro/index.tsx +622 -0
  28. package/src/components/navigation-menu.tsx +153 -0
  29. package/src/components/pagination-static.tsx +348 -0
  30. package/src/components/pagination.tsx +138 -0
  31. package/src/components/phone-input.tsx +276 -0
  32. package/src/components/sidebar.tsx +866 -0
  33. package/src/components/sonner.tsx +31 -0
  34. package/src/components/ssr-pagination.tsx +237 -0
  35. package/src/hooks/index.ts +19 -0
  36. package/src/hooks/useCfgRouter.ts +153 -0
  37. package/src/hooks/useLocalStorage.ts +221 -0
  38. package/src/hooks/useQueryParams.ts +73 -0
  39. package/src/hooks/useSessionStorage.ts +188 -0
  40. package/src/hooks/useTheme.ts +57 -0
  41. package/src/index.ts +24 -0
  42. package/src/lib/index.ts +2 -0
  43. package/src/styles/index.css +2 -0
  44. package/src/theme/ForceTheme.tsx +115 -0
  45. package/src/theme/ThemeProvider.tsx +82 -0
  46. package/src/theme/ThemeToggle.tsx +52 -0
  47. package/src/theme/index.ts +3 -0
  48. package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
  49. package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
  50. package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
  51. package/src/tools/JsonForm/index.ts +46 -0
  52. package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
  53. package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
  54. package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
  55. package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
  56. package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
  57. package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
  58. package/src/tools/JsonForm/templates/index.ts +12 -0
  59. package/src/tools/JsonForm/types.ts +83 -0
  60. package/src/tools/JsonForm/utils.ts +212 -0
  61. package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
  62. package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
  63. package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
  64. package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
  65. package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
  66. package/src/tools/JsonForm/widgets/index.ts +12 -0
  67. package/src/tools/JsonTree/index.tsx +252 -0
  68. package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
  69. package/src/tools/LottiePlayer/index.tsx +54 -0
  70. package/src/tools/LottiePlayer/types.ts +108 -0
  71. package/src/tools/LottiePlayer/useLottie.ts +163 -0
  72. package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
  73. package/src/tools/Mermaid/index.tsx +40 -0
  74. package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
  75. package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
  76. package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
  77. package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
  78. package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
  79. package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
  80. package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
  81. package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
  82. package/src/tools/OpenapiViewer/components/index.ts +14 -0
  83. package/src/tools/OpenapiViewer/constants.ts +39 -0
  84. package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
  85. package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
  86. package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
  87. package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
  88. package/src/tools/OpenapiViewer/index.tsx +36 -0
  89. package/src/tools/OpenapiViewer/types.ts +152 -0
  90. package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
  91. package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
  92. package/src/tools/OpenapiViewer/utils/index.ts +9 -0
  93. package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
  94. package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
  95. package/src/tools/PrettyCode/index.tsx +43 -0
  96. package/src/tools/VideoPlayer/README.md +239 -0
  97. package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
  98. package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
  99. package/src/tools/VideoPlayer/index.ts +9 -0
  100. package/src/tools/VideoPlayer/types.ts +62 -0
  101. package/src/tools/index.ts +43 -0
@@ -0,0 +1,276 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { AsYouType, parsePhoneNumberFromString, CountryCode, getCountries, getCountryCallingCode } from 'libphonenumber-js'
5
+ import { Input, Button } from "@djangocfg/ui-core/components"
6
+ import { cn } from "@djangocfg/ui-core/lib"
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuTrigger,
12
+ } from "./dropdown-menu"
13
+ import { ChevronDown, Search } from "lucide-react"
14
+
15
+ // Generate country flag emoji from country code
16
+ const getCountryFlag = (countryCode: CountryCode): string => {
17
+ return countryCode
18
+ .toUpperCase()
19
+ .replace(/./g, char => String.fromCodePoint(char.charCodeAt(0) + 127397))
20
+ }
21
+
22
+ // Get country name from country code using browser's built-in Intl.DisplayNames
23
+ const getCountryName = (countryCode: CountryCode): string => {
24
+ try {
25
+ const displayNames = new Intl.DisplayNames(['en'], { type: 'region' })
26
+ return displayNames.of(countryCode) || countryCode
27
+ } catch {
28
+ // Fallback for unsupported country codes
29
+ return countryCode
30
+ }
31
+ }
32
+
33
+ // Generate all countries from libphonenumber-js
34
+ const getAllCountries = () => {
35
+ return getCountries().map(countryCode => ({
36
+ code: countryCode,
37
+ name: getCountryName(countryCode),
38
+ flag: getCountryFlag(countryCode),
39
+ dialCode: `+${getCountryCallingCode(countryCode)}`
40
+ })).sort((a, b) => a.name.localeCompare(b.name))
41
+ }
42
+
43
+ const COUNTRIES = getAllCountries()
44
+
45
+ export interface PhoneInputProps {
46
+ value?: string
47
+ onChange?: (value: string | undefined) => void
48
+ defaultCountry?: CountryCode
49
+ className?: string
50
+ placeholder?: string
51
+ disabled?: boolean
52
+ }
53
+
54
+ const PhoneInput = React.forwardRef<HTMLInputElement, PhoneInputProps>(
55
+ ({
56
+ className,
57
+ value = '',
58
+ onChange,
59
+ defaultCountry = 'US',
60
+ placeholder = "Enter phone number",
61
+ disabled = false,
62
+ ...props
63
+ }, ref) => {
64
+ const [selectedCountry, setSelectedCountry] = React.useState<CountryCode>(defaultCountry)
65
+ const [inputValue, setInputValue] = React.useState('')
66
+ const [isDropdownOpen, setIsDropdownOpen] = React.useState(false)
67
+ const [searchQuery, setSearchQuery] = React.useState('')
68
+ const [highlightedIndex, setHighlightedIndex] = React.useState(-1)
69
+
70
+ // Find country data
71
+ const currentCountry = COUNTRIES.find(c => c.code === selectedCountry) || COUNTRIES[0]!
72
+
73
+ // Filter countries based on search query
74
+ const filteredCountries = React.useMemo(() => {
75
+ if (!searchQuery.trim()) return COUNTRIES
76
+
77
+ const query = searchQuery.toLowerCase()
78
+ return COUNTRIES.filter(country =>
79
+ country.name.toLowerCase().includes(query) ||
80
+ country.dialCode.includes(query) ||
81
+ country.code.toLowerCase().includes(query)
82
+ )
83
+ }, [searchQuery])
84
+
85
+ // Initialize input value from props
86
+ React.useEffect(() => {
87
+ if (value) {
88
+ try {
89
+ const phoneNumber = parsePhoneNumberFromString(value)
90
+ if (phoneNumber) {
91
+ setSelectedCountry(phoneNumber.country || defaultCountry)
92
+ setInputValue(phoneNumber.nationalNumber)
93
+ } else {
94
+ setInputValue(value)
95
+ }
96
+ } catch {
97
+ setInputValue(value)
98
+ }
99
+ }
100
+ }, [value, defaultCountry])
101
+
102
+ // Reset highlighted index when filtered countries change
103
+ React.useEffect(() => {
104
+ setHighlightedIndex(-1)
105
+ }, [filteredCountries])
106
+
107
+ // Reset search when dropdown closes
108
+ React.useEffect(() => {
109
+ if (!isDropdownOpen) {
110
+ setSearchQuery('')
111
+ setHighlightedIndex(-1)
112
+ }
113
+ }, [isDropdownOpen])
114
+
115
+ // Handle country selection
116
+ const handleCountrySelect = (country: typeof COUNTRIES[0]) => {
117
+ setSelectedCountry(country.code)
118
+ setIsDropdownOpen(false)
119
+ setSearchQuery('')
120
+ setHighlightedIndex(-1)
121
+
122
+ // Format existing number for new country
123
+ if (inputValue) {
124
+ const formatter = new AsYouType(country.code)
125
+ const formatted = formatter.input(inputValue)
126
+ setInputValue(formatted)
127
+
128
+ // Get E.164 format for onChange
129
+ const phoneNumber = formatter.getNumber()
130
+ onChange?.(phoneNumber?.number)
131
+ }
132
+ }
133
+
134
+ // Handle keyboard navigation
135
+ const handleKeyDown = (e: React.KeyboardEvent) => {
136
+ if (!isDropdownOpen) return
137
+
138
+ switch (e.key) {
139
+ case 'ArrowDown':
140
+ e.preventDefault()
141
+ setHighlightedIndex(prev =>
142
+ prev < filteredCountries.length - 1 ? prev + 1 : 0
143
+ )
144
+ break
145
+ case 'ArrowUp':
146
+ e.preventDefault()
147
+ setHighlightedIndex(prev =>
148
+ prev > 0 ? prev - 1 : filteredCountries.length - 1
149
+ )
150
+ break
151
+ case 'Enter':
152
+ e.preventDefault()
153
+ if (highlightedIndex >= 0 && highlightedIndex < filteredCountries.length) {
154
+ handleCountrySelect(filteredCountries[highlightedIndex]!)
155
+ }
156
+ break
157
+ case 'Escape':
158
+ e.preventDefault()
159
+ setIsDropdownOpen(false)
160
+ break
161
+ }
162
+ }
163
+
164
+ // Handle input change
165
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
166
+ const input = e.target.value
167
+
168
+ // Use AsYouType formatter for real-time formatting
169
+ const formatter = new AsYouType(selectedCountry)
170
+ const formatted = formatter.input(input)
171
+
172
+ setInputValue(formatted)
173
+
174
+ // Get the parsed phone number for validation and E.164 format
175
+ const phoneNumber = formatter.getNumber()
176
+ onChange?.(phoneNumber?.number)
177
+ }
178
+
179
+ // Handle paste events to extract phone numbers
180
+ const handlePaste = (e: React.ClipboardEvent<HTMLInputElement>) => {
181
+ const pastedText = e.clipboardData.getData('text')
182
+
183
+ try {
184
+ // Try to parse as international number first
185
+ const phoneNumber = parsePhoneNumberFromString(pastedText)
186
+ if (phoneNumber) {
187
+ e.preventDefault()
188
+ setSelectedCountry(phoneNumber.country || selectedCountry)
189
+ setInputValue(phoneNumber.nationalNumber)
190
+ onChange?.(phoneNumber.number)
191
+ return
192
+ }
193
+ } catch {
194
+ // Let default paste behavior handle it
195
+ }
196
+ }
197
+
198
+ return (
199
+ <div className={cn("relative flex", className)} onKeyDown={handleKeyDown}>
200
+ {/* Country Dropdown */}
201
+ <DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
202
+ <DropdownMenuTrigger asChild>
203
+ <Button
204
+ variant="outline"
205
+ size="sm"
206
+ className="h-10 px-3 rounded-r-none border-r-0 flex items-center gap-2"
207
+ disabled={disabled}
208
+ >
209
+ <span className="text-base">{currentCountry.flag}</span>
210
+ <span className="text-sm font-mono">{currentCountry.dialCode}</span>
211
+ <ChevronDown className="h-3 w-3 opacity-50" />
212
+ </Button>
213
+ </DropdownMenuTrigger>
214
+ <DropdownMenuContent align="start" className="w-80 max-h-80 p-0">
215
+ {/* Search Input */}
216
+ <div className="p-2 border-b">
217
+ <div className="relative">
218
+ <Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
219
+ <Input
220
+ placeholder="Search countries..."
221
+ value={searchQuery}
222
+ onChange={(e) => setSearchQuery(e.target.value)}
223
+ className="pl-8 h-8"
224
+ autoFocus
225
+ />
226
+ </div>
227
+ </div>
228
+
229
+ {/* Countries List */}
230
+ <div className="max-h-60 overflow-y-auto">
231
+ {filteredCountries.length === 0 ? (
232
+ <div className="p-4 text-sm text-muted-foreground text-center">
233
+ No countries found
234
+ </div>
235
+ ) : (
236
+ filteredCountries.map((country, index) => (
237
+ <DropdownMenuItem
238
+ key={country.code}
239
+ onClick={() => handleCountrySelect(country)}
240
+ className={cn(
241
+ "flex items-center gap-3 px-3 py-2 cursor-pointer",
242
+ index === highlightedIndex && "bg-accent"
243
+ )}
244
+ >
245
+ <span className="text-base">{country.flag}</span>
246
+ <span className="flex-1 text-sm">{country.name}</span>
247
+ <span className="text-sm font-mono text-muted-foreground">
248
+ {country.dialCode}
249
+ </span>
250
+ </DropdownMenuItem>
251
+ ))
252
+ )}
253
+ </div>
254
+ </DropdownMenuContent>
255
+ </DropdownMenu>
256
+
257
+ {/* Phone Input */}
258
+ <Input
259
+ ref={ref}
260
+ type="tel"
261
+ value={inputValue}
262
+ onChange={handleInputChange}
263
+ onPaste={handlePaste}
264
+ placeholder={placeholder}
265
+ disabled={disabled}
266
+ className="rounded-l-none border-l-0 flex-1"
267
+ {...props}
268
+ />
269
+ </div>
270
+ )
271
+ }
272
+ )
273
+
274
+ PhoneInput.displayName = "PhoneInput"
275
+
276
+ export { PhoneInput }