@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.
- package/LICENSE +21 -0
- package/README.md +152 -0
- package/package.json +110 -0
- package/src/animations/AnimatedBackground.tsx +645 -0
- package/src/animations/index.ts +2 -0
- package/src/blocks/ArticleCard.tsx +94 -0
- package/src/blocks/ArticleList.tsx +95 -0
- package/src/blocks/CTASection.tsx +136 -0
- package/src/blocks/FeatureSection.tsx +104 -0
- package/src/blocks/Hero.tsx +102 -0
- package/src/blocks/NewsletterSection.tsx +119 -0
- package/src/blocks/StatsSection.tsx +103 -0
- package/src/blocks/SuperHero.tsx +328 -0
- package/src/blocks/TestimonialSection.tsx +122 -0
- package/src/blocks/index.ts +9 -0
- package/src/components/README.md +2018 -0
- package/src/components/breadcrumb-navigation.tsx +127 -0
- package/src/components/breadcrumb.tsx +132 -0
- package/src/components/button-download.tsx +275 -0
- package/src/components/dropdown-menu.tsx +219 -0
- package/src/components/index.ts +86 -0
- package/src/components/markdown/MarkdownMessage.tsx +338 -0
- package/src/components/markdown/index.ts +5 -0
- package/src/components/menubar.tsx +274 -0
- package/src/components/multi-select-pro/async.tsx +608 -0
- package/src/components/multi-select-pro/helpers.tsx +84 -0
- package/src/components/multi-select-pro/index.tsx +622 -0
- package/src/components/navigation-menu.tsx +153 -0
- package/src/components/pagination-static.tsx +348 -0
- package/src/components/pagination.tsx +138 -0
- package/src/components/phone-input.tsx +276 -0
- package/src/components/sidebar.tsx +866 -0
- package/src/components/sonner.tsx +31 -0
- package/src/components/ssr-pagination.tsx +237 -0
- package/src/hooks/index.ts +19 -0
- package/src/hooks/useCfgRouter.ts +153 -0
- package/src/hooks/useLocalStorage.ts +221 -0
- package/src/hooks/useQueryParams.ts +73 -0
- package/src/hooks/useSessionStorage.ts +188 -0
- package/src/hooks/useTheme.ts +57 -0
- package/src/index.ts +24 -0
- package/src/lib/index.ts +2 -0
- package/src/styles/index.css +2 -0
- package/src/theme/ForceTheme.tsx +115 -0
- package/src/theme/ThemeProvider.tsx +82 -0
- package/src/theme/ThemeToggle.tsx +52 -0
- package/src/theme/index.ts +3 -0
- package/src/tools/JsonForm/JsonSchemaForm.tsx +199 -0
- package/src/tools/JsonForm/examples/BotConfigExample.tsx +245 -0
- package/src/tools/JsonForm/examples/RealBotConfigExample.tsx +157 -0
- package/src/tools/JsonForm/index.ts +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldItemTemplate.tsx +46 -0
- package/src/tools/JsonForm/templates/ArrayFieldTemplate.tsx +73 -0
- package/src/tools/JsonForm/templates/BaseInputTemplate.tsx +106 -0
- package/src/tools/JsonForm/templates/ErrorListTemplate.tsx +34 -0
- package/src/tools/JsonForm/templates/FieldTemplate.tsx +61 -0
- package/src/tools/JsonForm/templates/ObjectFieldTemplate.tsx +43 -0
- package/src/tools/JsonForm/templates/index.ts +12 -0
- package/src/tools/JsonForm/types.ts +83 -0
- package/src/tools/JsonForm/utils.ts +212 -0
- package/src/tools/JsonForm/widgets/CheckboxWidget.tsx +36 -0
- package/src/tools/JsonForm/widgets/NumberWidget.tsx +88 -0
- package/src/tools/JsonForm/widgets/SelectWidget.tsx +100 -0
- package/src/tools/JsonForm/widgets/SwitchWidget.tsx +34 -0
- package/src/tools/JsonForm/widgets/TextWidget.tsx +95 -0
- package/src/tools/JsonForm/widgets/index.ts +12 -0
- package/src/tools/JsonTree/index.tsx +252 -0
- package/src/tools/LottiePlayer/LottiePlayer.client.tsx +212 -0
- package/src/tools/LottiePlayer/index.tsx +54 -0
- package/src/tools/LottiePlayer/types.ts +108 -0
- package/src/tools/LottiePlayer/useLottie.ts +163 -0
- package/src/tools/Mermaid/Mermaid.client.tsx +341 -0
- package/src/tools/Mermaid/index.tsx +40 -0
- package/src/tools/OpenapiViewer/components/EndpointInfo.tsx +144 -0
- package/src/tools/OpenapiViewer/components/EndpointsLibrary.tsx +255 -0
- package/src/tools/OpenapiViewer/components/PlaygroundLayout.tsx +123 -0
- package/src/tools/OpenapiViewer/components/PlaygroundStepper.tsx +98 -0
- package/src/tools/OpenapiViewer/components/RequestBuilder.tsx +164 -0
- package/src/tools/OpenapiViewer/components/RequestParametersForm.tsx +253 -0
- package/src/tools/OpenapiViewer/components/ResponseViewer.tsx +169 -0
- package/src/tools/OpenapiViewer/components/VersionSelector.tsx +64 -0
- package/src/tools/OpenapiViewer/components/index.ts +14 -0
- package/src/tools/OpenapiViewer/constants.ts +39 -0
- package/src/tools/OpenapiViewer/context/PlaygroundContext.tsx +338 -0
- package/src/tools/OpenapiViewer/hooks/index.ts +8 -0
- package/src/tools/OpenapiViewer/hooks/useMobile.ts +10 -0
- package/src/tools/OpenapiViewer/hooks/useOpenApiSchema.ts +203 -0
- package/src/tools/OpenapiViewer/index.tsx +36 -0
- package/src/tools/OpenapiViewer/types.ts +152 -0
- package/src/tools/OpenapiViewer/utils/apiKeyManager.ts +149 -0
- package/src/tools/OpenapiViewer/utils/formatters.ts +71 -0
- package/src/tools/OpenapiViewer/utils/index.ts +9 -0
- package/src/tools/OpenapiViewer/utils/versionManager.ts +161 -0
- package/src/tools/PrettyCode/PrettyCode.client.tsx +217 -0
- package/src/tools/PrettyCode/index.tsx +43 -0
- package/src/tools/VideoPlayer/README.md +239 -0
- package/src/tools/VideoPlayer/VideoControls.tsx +138 -0
- package/src/tools/VideoPlayer/VideoPlayer.tsx +230 -0
- package/src/tools/VideoPlayer/index.ts +9 -0
- package/src/tools/VideoPlayer/types.ts +62 -0
- 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 }
|