@djangocfg/ui-core 2.1.162 → 2.1.164

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.162",
3
+ "version": "2.1.164",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -76,7 +76,7 @@
76
76
  "playground": "playground dev"
77
77
  },
78
78
  "peerDependencies": {
79
- "@djangocfg/i18n": "^2.1.162",
79
+ "@djangocfg/i18n": "^2.1.164",
80
80
  "react-device-detect": "^2.2.3",
81
81
  "consola": "^3.4.2",
82
82
  "lucide-react": "^0.545.0",
@@ -138,9 +138,9 @@
138
138
  "vaul": "1.1.2"
139
139
  },
140
140
  "devDependencies": {
141
- "@djangocfg/i18n": "^2.1.162",
141
+ "@djangocfg/i18n": "^2.1.164",
142
142
  "@djangocfg/playground": "workspace:*",
143
- "@djangocfg/typescript-config": "^2.1.162",
143
+ "@djangocfg/typescript-config": "^2.1.164",
144
144
  "@types/node": "^24.7.2",
145
145
  "@types/react": "^19.1.0",
146
146
  "@types/react-dom": "^19.1.0",
@@ -23,7 +23,7 @@ export { MultiSelectPro } from './multi-select-pro';
23
23
  export type { MultiSelectProOption, MultiSelectProGroup, MultiSelectProProps, MultiSelectProRef, AnimationConfig, ResponsiveConfig } from './multi-select-pro';
24
24
  export { CountrySelect, getEmojiFlag } from './country-select';
25
25
  export type { CountrySelectProps, CountrySelectVariant, CountryOption, TCountryCode } from './country-select';
26
- export { LanguageSelect } from './language-select';
26
+ export { LanguageSelect, getLanguageFlag, LANGUAGE_TO_COUNTRY } from './language-select';
27
27
  export type { LanguageSelectProps, LanguageSelectVariant, LanguageOption, TLanguageCode } from './language-select';
28
28
  export { MultiSelectProAsync } from './multi-select-pro/async';
29
29
  export type { MultiSelectProAsyncProps } from './multi-select-pro/async';
@@ -48,7 +48,7 @@ export type { PortalProps } from './portal';
48
48
  // Overlay Components
49
49
  export { Dialog, DialogTrigger, DialogClose, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription } from './dialog';
50
50
  export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from './alert-dialog';
51
- export { Popover, PopoverContent, PopoverTrigger } from './popover';
51
+ export { Popover, PopoverContent, PopoverTrigger, PopoverAnchor, PopoverArrow } from './popover';
52
52
  export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription } from './sheet';
53
53
  export { Drawer, DrawerTrigger, DrawerClose, DrawerContent, DrawerHeader, DrawerFooter, DrawerTitle, DrawerDescription } from './drawer';
54
54
  export { ResponsiveSheet, ResponsiveSheetContent, ResponsiveSheetHeader, ResponsiveSheetTitle, ResponsiveSheetDescription, ResponsiveSheetFooter } from './responsive-sheet';
@@ -2,9 +2,274 @@
2
2
 
3
3
  import { Check, ChevronsUpDown, Search, X } from 'lucide-react';
4
4
  import * as React from 'react';
5
- import { languages, type TLanguageCode } from 'countries-list';
5
+ import { languages, getEmojiFlag, type TLanguageCode, type TCountryCode } from 'countries-list';
6
6
 
7
7
  import { cn } from '../lib/utils';
8
+
9
+ /**
10
+ * Mapping of language codes (ISO 639-1) to primary country codes (ISO 3166-1 alpha-2)
11
+ * Used to display country flag emoji for each language
12
+ *
13
+ * Note: Some languages are spoken in multiple countries, we pick the most representative one.
14
+ */
15
+ const LANGUAGE_TO_COUNTRY: Partial<Record<TLanguageCode, TCountryCode>> = {
16
+ // Major world languages
17
+ en: 'US', // English → United States
18
+ zh: 'CN', // Chinese → China
19
+ es: 'ES', // Spanish → Spain
20
+ ar: 'SA', // Arabic → Saudi Arabia
21
+ hi: 'IN', // Hindi → India
22
+ bn: 'BD', // Bengali → Bangladesh
23
+ pt: 'BR', // Portuguese → Brazil
24
+ ru: 'RU', // Russian → Russia
25
+ ja: 'JP', // Japanese → Japan
26
+ de: 'DE', // German → Germany
27
+ fr: 'FR', // French → France
28
+ ko: 'KR', // Korean → South Korea
29
+ it: 'IT', // Italian → Italy
30
+ tr: 'TR', // Turkish → Turkey
31
+ vi: 'VN', // Vietnamese → Vietnam
32
+ pl: 'PL', // Polish → Poland
33
+ uk: 'UA', // Ukrainian → Ukraine
34
+ nl: 'NL', // Dutch → Netherlands
35
+
36
+ // European languages
37
+ el: 'GR', // Greek → Greece
38
+ cs: 'CZ', // Czech → Czech Republic
39
+ sv: 'SE', // Swedish → Sweden
40
+ hu: 'HU', // Hungarian → Hungary
41
+ ro: 'RO', // Romanian → Romania
42
+ da: 'DK', // Danish → Denmark
43
+ fi: 'FI', // Finnish → Finland
44
+ no: 'NO', // Norwegian → Norway
45
+ nb: 'NO', // Norwegian Bokmål → Norway
46
+ nn: 'NO', // Norwegian Nynorsk → Norway
47
+ sk: 'SK', // Slovak → Slovakia
48
+ bg: 'BG', // Bulgarian → Bulgaria
49
+ hr: 'HR', // Croatian → Croatia
50
+ sr: 'RS', // Serbian → Serbia
51
+ sl: 'SI', // Slovenian → Slovenia
52
+ et: 'EE', // Estonian → Estonia
53
+ lv: 'LV', // Latvian → Latvia
54
+ lt: 'LT', // Lithuanian → Lithuania
55
+ be: 'BY', // Belarusian → Belarus
56
+ mk: 'MK', // Macedonian → North Macedonia
57
+ sq: 'AL', // Albanian → Albania
58
+ is: 'IS', // Icelandic → Iceland
59
+ mt: 'MT', // Maltese → Malta
60
+ ga: 'IE', // Irish → Ireland
61
+ cy: 'GB', // Welsh → United Kingdom
62
+ gd: 'GB', // Scottish Gaelic → United Kingdom
63
+ br: 'FR', // Breton → France
64
+ lb: 'LU', // Luxembourgish → Luxembourg
65
+ fo: 'FO', // Faroese → Faroe Islands
66
+ bs: 'BA', // Bosnian → Bosnia
67
+
68
+ // Spanish regional
69
+ eu: 'ES', // Basque → Spain
70
+ ca: 'ES', // Catalan → Spain
71
+ gl: 'ES', // Galician → Spain
72
+
73
+ // Asian languages
74
+ th: 'TH', // Thai → Thailand
75
+ id: 'ID', // Indonesian → Indonesia
76
+ ms: 'MY', // Malay → Malaysia
77
+ tl: 'PH', // Tagalog → Philippines
78
+ jv: 'ID', // Javanese → Indonesia
79
+ su: 'ID', // Sundanese → Indonesia
80
+
81
+ // South Asian languages
82
+ ta: 'IN', // Tamil → India
83
+ te: 'IN', // Telugu → India
84
+ mr: 'IN', // Marathi → India
85
+ gu: 'IN', // Gujarati → India
86
+ kn: 'IN', // Kannada → India
87
+ ml: 'IN', // Malayalam → India
88
+ pa: 'IN', // Punjabi → India
89
+ or: 'IN', // Odia → India
90
+ as: 'IN', // Assamese → India
91
+ ne: 'NP', // Nepali → Nepal
92
+ si: 'LK', // Sinhala → Sri Lanka
93
+ dz: 'BT', // Dzongkha → Bhutan
94
+
95
+ // Middle East & Central Asia
96
+ fa: 'IR', // Persian/Farsi → Iran
97
+ ur: 'PK', // Urdu → Pakistan
98
+ ps: 'AF', // Pashto → Afghanistan
99
+ he: 'IL', // Hebrew → Israel
100
+ ku: 'IQ', // Kurdish → Iraq
101
+ hy: 'AM', // Armenian → Armenia
102
+ ka: 'GE', // Georgian → Georgia
103
+ az: 'AZ', // Azerbaijani → Azerbaijan
104
+ kk: 'KZ', // Kazakh → Kazakhstan
105
+ uz: 'UZ', // Uzbek → Uzbekistan
106
+ tg: 'TJ', // Tajik → Tajikistan
107
+ ky: 'KG', // Kyrgyz → Kyrgyzstan
108
+ tk: 'TM', // Turkmen → Turkmenistan
109
+ mn: 'MN', // Mongolian → Mongolia
110
+
111
+ // Southeast Asian
112
+ my: 'MM', // Burmese → Myanmar
113
+ km: 'KH', // Khmer → Cambodia
114
+ lo: 'LA', // Lao → Laos
115
+
116
+ // African languages
117
+ sw: 'KE', // Swahili → Kenya
118
+ am: 'ET', // Amharic → Ethiopia
119
+ ha: 'NG', // Hausa → Nigeria
120
+ yo: 'NG', // Yoruba → Nigeria
121
+ ig: 'NG', // Igbo → Nigeria
122
+ zu: 'ZA', // Zulu → South Africa
123
+ xh: 'ZA', // Xhosa → South Africa
124
+ af: 'ZA', // Afrikaans → South Africa
125
+ st: 'ZA', // Southern Sotho → South Africa
126
+ tn: 'BW', // Tswana → Botswana
127
+ sn: 'ZW', // Shona → Zimbabwe
128
+ rw: 'RW', // Kinyarwanda → Rwanda
129
+ rn: 'BI', // Kirundi → Burundi
130
+ so: 'SO', // Somali → Somalia
131
+ ti: 'ER', // Tigrinya → Eritrea
132
+ mg: 'MG', // Malagasy → Madagascar
133
+ ny: 'MW', // Chichewa → Malawi
134
+ lg: 'UG', // Luganda → Uganda
135
+ wo: 'SN', // Wolof → Senegal
136
+ ff: 'SN', // Fulah → Senegal
137
+ bm: 'ML', // Bambara → Mali
138
+
139
+ // Caribbean & Creole
140
+ ht: 'HT', // Haitian Creole → Haiti
141
+
142
+ // Pacific
143
+ mi: 'NZ', // Maori → New Zealand
144
+ sm: 'WS', // Samoan → Samoa
145
+ to: 'TO', // Tongan → Tonga
146
+ fj: 'FJ', // Fijian → Fiji
147
+ mh: 'MH', // Marshallese → Marshall Islands
148
+ ty: 'PF', // Tahitian → French Polynesia
149
+ na: 'NR', // Nauruan → Nauru
150
+ bi: 'VU', // Bislama → Vanuatu
151
+ ch: 'GU', // Chamorro → Guam
152
+
153
+ // Other constructed/historical
154
+ // Note: Constructed languages have no country, using symbolic mappings
155
+ // eo: Esperanto - no country flag available
156
+ la: 'VA', // Latin → Vatican
157
+ // ia: Interlingua - no country flag available
158
+ // ie: Interlingue - no country flag available
159
+ // io: Ido - no country flag available
160
+ // vo: Volapük - no country flag available
161
+
162
+ // China minority languages
163
+ bo: 'CN', // Tibetan → China
164
+ ug: 'CN', // Uyghur → China
165
+ za: 'CN', // Zhuang → China
166
+ ii: 'CN', // Sichuan Yi → China
167
+
168
+ // South Asia
169
+ dv: 'MV', // Dhivehi → Maldives
170
+ ks: 'IN', // Kashmiri → India
171
+ sd: 'PK', // Sindhi → Pakistan
172
+ sa: 'IN', // Sanskrit → India
173
+ pi: 'IN', // Pali → India (historical Buddhist)
174
+
175
+ // Baltic-Finnic
176
+ fy: 'NL', // Western Frisian → Netherlands
177
+
178
+ // Caucasian
179
+ os: 'RU', // Ossetic → Russia
180
+ ce: 'RU', // Chechen → Russia
181
+ av: 'RU', // Avar → Russia
182
+ ab: 'GE', // Abkhazian → Georgia
183
+
184
+ // Turkic in Russia
185
+ tt: 'RU', // Tatar → Russia
186
+ ba: 'RU', // Bashkir → Russia
187
+ cv: 'RU', // Chuvash → Russia
188
+
189
+ // Russia minority
190
+ kv: 'RU', // Komi → Russia
191
+
192
+ // Africa additional
193
+ aa: 'ET', // Afar → Ethiopia
194
+ ak: 'GH', // Akan → Ghana
195
+ ee: 'GH', // Ewe → Ghana
196
+ tw: 'GH', // Twi → Ghana
197
+ ln: 'CD', // Lingala → DR Congo
198
+ lu: 'CD', // Luba-Katanga → DR Congo
199
+ kg: 'CD', // Kongo → DR Congo
200
+ sg: 'CF', // Sango → Central African Republic
201
+ ki: 'KE', // Kikuyu → Kenya
202
+ om: 'ET', // Oromo → Ethiopia
203
+ kr: 'NE', // Kanuri → Niger
204
+ kj: 'NA', // Kuanyama → Namibia
205
+ hz: 'NA', // Herero → Namibia
206
+ ng: 'NA', // Ndonga → Namibia
207
+ nd: 'ZW', // North Ndebele → Zimbabwe
208
+ nr: 'ZA', // South Ndebele → South Africa
209
+ ss: 'SZ', // Swati → Eswatini (Swaziland)
210
+ ts: 'ZA', // Tsonga → South Africa
211
+ ve: 'ZA', // Venda → South Africa
212
+
213
+ // Americas indigenous
214
+ gn: 'PY', // Guarani → Paraguay
215
+ qu: 'PE', // Quechua → Peru
216
+ ay: 'BO', // Aymara → Bolivia
217
+ nv: 'US', // Navajo → United States
218
+ oj: 'CA', // Ojibwa → Canada
219
+ cr: 'CA', // Cree → Canada
220
+ iu: 'CA', // Inuktitut → Canada
221
+ ik: 'US', // Inupiak → United States (Alaska)
222
+
223
+ // Greenland
224
+ kl: 'GL', // Greenlandic → Greenland
225
+
226
+ // Other European
227
+ an: 'ES', // Aragonese → Spain
228
+ wa: 'BE', // Walloon → Belgium
229
+ li: 'NL', // Limburgian → Netherlands
230
+
231
+ // Slavic historical
232
+ cu: 'BG', // Old Church Slavonic → Bulgaria
233
+ mo: 'MD', // Moldovan → Moldova
234
+
235
+ // Jewish
236
+ yi: 'IL', // Yiddish → Israel
237
+
238
+ // Bihari (group)
239
+ bh: 'IN', // Bihari → India
240
+
241
+ // Papua New Guinea
242
+ ho: 'PG', // Hiri Motu → Papua New Guinea
243
+
244
+ // Other Slavic
245
+ sh: 'RS', // Serbo-Croatian → Serbia
246
+
247
+ // Romance
248
+ oc: 'FR', // Occitan → France
249
+ co: 'FR', // Corsican → France
250
+ sc: 'IT', // Sardinian → Italy
251
+ rm: 'CH', // Romansh → Switzerland
252
+
253
+ // Germanic
254
+ gv: 'IM', // Manx → Isle of Man
255
+ kw: 'GB', // Cornish → United Kingdom
256
+
257
+ // Uralic
258
+ se: 'NO', // Northern Sami → Norway
259
+
260
+ // Note: ch (Chamorro) already defined above in Pacific section
261
+ };
262
+
263
+ /**
264
+ * Get emoji flag for a language code
265
+ * @param languageCode - ISO 639-1 language code (e.g., 'en', 'ru', 'ko')
266
+ * @returns Emoji flag string or empty string if no mapping exists
267
+ */
268
+ export function getLanguageFlag(languageCode: TLanguageCode | string): string {
269
+ const countryCode = LANGUAGE_TO_COUNTRY[languageCode as TLanguageCode];
270
+ if (!countryCode) return '';
271
+ return getEmojiFlag(countryCode);
272
+ }
8
273
  import { Badge } from './badge';
9
274
  import { Button } from './button';
10
275
  import { Checkbox } from './checkbox';
@@ -19,6 +284,8 @@ export interface LanguageOption {
19
284
  code: TLanguageCode;
20
285
  name: string;
21
286
  native: string;
287
+ /** Emoji flag (if available) */
288
+ flag: string;
22
289
  }
23
290
 
24
291
  export type LanguageSelectVariant = 'dropdown' | 'inline';
@@ -52,6 +319,10 @@ export interface LanguageSelectProps {
52
319
  allowedLanguages?: TLanguageCode[];
53
320
  /** Exclude specific language codes */
54
321
  excludedLanguages?: TLanguageCode[];
322
+ /** Show flag emoji (default: true) */
323
+ showFlag?: boolean;
324
+ /** Show language code (default: false) */
325
+ showCode?: boolean;
55
326
  /** Max height for inline variant */
56
327
  maxHeight?: number;
57
328
  /** Show search input */
@@ -125,6 +396,8 @@ export function LanguageSelect({
125
396
  showNativeName = false,
126
397
  allowedLanguages,
127
398
  excludedLanguages,
399
+ showFlag = true,
400
+ showCode = false,
128
401
  maxHeight = 300,
129
402
  showSearch = true,
130
403
  selectedCountLabel = (count: number) => `${count} selected`,
@@ -155,6 +428,7 @@ export function LanguageSelect({
155
428
  code,
156
429
  name: getLanguageName?.(code) ?? languages[code].name,
157
430
  native: languages[code].native,
431
+ flag: getLanguageFlag(code),
158
432
  }))
159
433
  .sort((a, b) => a.name.localeCompare(b.name));
160
434
  }, [getLanguageName, allowedLanguages, excludedLanguages]);
@@ -231,8 +505,17 @@ export function LanguageSelect({
231
505
  filteredLanguages.map((language) => {
232
506
  const isSelected = value.includes(language.code);
233
507
  return (
234
- <label
508
+ <div
235
509
  key={language.code}
510
+ role="button"
511
+ tabIndex={disabled ? -1 : 0}
512
+ onClick={() => !disabled && handleSelect(language.code)}
513
+ onKeyDown={(e) => {
514
+ if (!disabled && (e.key === 'Enter' || e.key === ' ')) {
515
+ e.preventDefault();
516
+ handleSelect(language.code);
517
+ }
518
+ }}
236
519
  className={cn(
237
520
  'flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer',
238
521
  'hover:bg-accent/50 transition-colors',
@@ -245,6 +528,7 @@ export function LanguageSelect({
245
528
  checked={isSelected}
246
529
  onCheckedChange={() => !disabled && handleSelect(language.code)}
247
530
  disabled={disabled}
531
+ onClick={(e) => e.stopPropagation()}
248
532
  />
249
533
  ) : (
250
534
  <div className={cn(
@@ -254,6 +538,9 @@ export function LanguageSelect({
254
538
  {isSelected && <div className="h-2 w-2 rounded-full bg-primary-foreground" />}
255
539
  </div>
256
540
  )}
541
+ {showFlag && language.flag && (
542
+ <span className="text-lg">{language.flag}</span>
543
+ )}
257
544
  <div className="flex flex-col flex-1 min-w-0">
258
545
  <span className={cn('text-sm', isSelected && 'font-medium')}>
259
546
  {language.name}
@@ -264,10 +551,12 @@ export function LanguageSelect({
264
551
  </span>
265
552
  )}
266
553
  </div>
267
- <span className="text-xs text-muted-foreground uppercase">
268
- {language.code}
269
- </span>
270
- </label>
554
+ {showCode && (
555
+ <span className="text-xs text-muted-foreground uppercase">
556
+ {language.code}
557
+ </span>
558
+ )}
559
+ </div>
271
560
  );
272
561
  })
273
562
  )}
@@ -287,7 +576,8 @@ export function LanguageSelect({
287
576
  const language = selectedLanguages[0]!;
288
577
  return (
289
578
  <div className="flex items-center gap-2">
290
- <span className="text-xs text-muted-foreground uppercase">{language.code}</span>
579
+ {showFlag && language.flag && <span>{language.flag}</span>}
580
+ {showCode && <span className="text-xs text-muted-foreground uppercase">{language.code}</span>}
291
581
  <span>{language.name}</span>
292
582
  </div>
293
583
  );
@@ -304,7 +594,8 @@ export function LanguageSelect({
304
594
  variant="secondary"
305
595
  className="mr-1 text-xs"
306
596
  >
307
- <span className="mr-1 text-muted-foreground uppercase">{language.code}</span>
597
+ {showFlag && language.flag && <span className="mr-1">{language.flag}</span>}
598
+ {showCode && <span className="mr-1 text-muted-foreground uppercase">{language.code}</span>}
308
599
  {language.name}
309
600
  <button
310
601
  className="ml-1 rounded-full hover:bg-muted-foreground/20"
@@ -323,7 +614,7 @@ export function LanguageSelect({
323
614
  )}
324
615
  </div>
325
616
  )
326
- }, [selectedLanguages, maxDisplay, resolvedPlaceholder, disabled, multiple, handleRemove, moreItemsLabel])
617
+ }, [selectedLanguages, maxDisplay, resolvedPlaceholder, disabled, multiple, handleRemove, moreItemsLabel, showFlag, showCode])
327
618
 
328
619
  return (
329
620
  <Popover
@@ -379,6 +670,9 @@ export function LanguageSelect({
379
670
  isSelected ? "opacity-100" : "opacity-0"
380
671
  )}
381
672
  />
673
+ {showFlag && language.flag && (
674
+ <span className="mr-2 text-lg">{language.flag}</span>
675
+ )}
382
676
  <div className="flex flex-col flex-1 min-w-0">
383
677
  <span className="truncate">{language.name}</span>
384
678
  {showNativeName && language.native !== language.name && (
@@ -387,9 +681,11 @@ export function LanguageSelect({
387
681
  </span>
388
682
  )}
389
683
  </div>
390
- <span className="text-xs text-muted-foreground uppercase ml-2">
391
- {language.code}
392
- </span>
684
+ {showCode && (
685
+ <span className="text-xs text-muted-foreground uppercase ml-2">
686
+ {language.code}
687
+ </span>
688
+ )}
393
689
  </CommandItem>
394
690
  )
395
691
  })}
@@ -404,3 +700,4 @@ export function LanguageSelect({
404
700
 
405
701
  // Re-export types for convenience
406
702
  export type { TLanguageCode } from 'countries-list';
703
+ export { LANGUAGE_TO_COUNTRY };
@@ -31,4 +31,16 @@ const PopoverContent = React.forwardRef<
31
31
  ))
32
32
  PopoverContent.displayName = PopoverPrimitive.Content.displayName
33
33
 
34
- export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
34
+ const PopoverArrow = React.forwardRef<
35
+ React.ElementRef<typeof PopoverPrimitive.Arrow>,
36
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Arrow>
37
+ >(({ className, ...props }, ref) => (
38
+ <PopoverPrimitive.Arrow
39
+ ref={ref}
40
+ className={cn('fill-popover', className)}
41
+ {...props}
42
+ />
43
+ ))
44
+ PopoverArrow.displayName = PopoverPrimitive.Arrow.displayName
45
+
46
+ export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor, PopoverArrow }