uibee 2.17.4 → 3.0.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 (183) hide show
  1. package/README.md +34 -0
  2. package/dist/icons-lZYQ6Vlr.js +125 -0
  3. package/dist/src/components/index.d.ts +706 -30
  4. package/dist/src/components/index.js +3243 -44
  5. package/dist/src/hooks/index.d.ts +15 -3
  6. package/dist/src/hooks/index.js +44 -3
  7. package/dist/src/icons/index.d.ts +420 -0
  8. package/dist/src/icons/index.js +3 -0
  9. package/dist/src/styles/index.d.ts +1 -1
  10. package/dist/src/styles/index.js +2 -1
  11. package/dist/src/utils/index.d.ts +38 -5
  12. package/dist/src/utils/index.js +125 -6
  13. package/dist/{src/globals.css → style.css} +1226 -607
  14. package/dist/useClickOutside-Cmp-RsP3.js +13 -0
  15. package/index.d.ts +0 -1
  16. package/package.json +22 -23
  17. package/src/components/alert/alert.tsx +20 -18
  18. package/src/components/buttons/button.tsx +28 -35
  19. package/src/components/confirm/confirmPopup.tsx +15 -17
  20. package/src/components/container/accordion.tsx +54 -0
  21. package/src/components/container/glassCard.tsx +14 -0
  22. package/src/components/container/iconBubble.tsx +30 -0
  23. package/src/components/container/leftBarPanel.tsx +15 -0
  24. package/src/components/container/pulseDot.tsx +31 -0
  25. package/src/components/container/statCard.tsx +24 -0
  26. package/src/components/container/tabs.tsx +64 -0
  27. package/src/components/footer/footer.tsx +124 -0
  28. package/src/components/footer/loginContent.tsx +70 -0
  29. package/src/components/index.ts +15 -0
  30. package/src/components/inputs/checkbox.tsx +4 -4
  31. package/src/components/inputs/input.tsx +11 -13
  32. package/src/components/inputs/multiSelect.tsx +144 -0
  33. package/src/components/inputs/radio.tsx +4 -4
  34. package/src/components/inputs/range.tsx +6 -5
  35. package/src/components/inputs/searchInput.tsx +1 -1
  36. package/src/components/inputs/select.tsx +19 -18
  37. package/src/components/inputs/shared/colorPickerPopup.tsx +5 -5
  38. package/src/components/inputs/shared/dateTimePickerPopup.tsx +9 -9
  39. package/src/components/inputs/shared/inputError.tsx +1 -1
  40. package/src/components/inputs/switch.tsx +7 -7
  41. package/src/components/inputs/tagInput.tsx +6 -7
  42. package/src/components/inputs/textarea.tsx +8 -10
  43. package/src/components/inputs/toggle.tsx +6 -6
  44. package/src/components/markdownrender/markdownRender.tsx +4 -2
  45. package/src/components/table/body.tsx +6 -6
  46. package/src/components/table/format.ts +1 -1
  47. package/src/components/table/header.tsx +1 -1
  48. package/src/components/table/pagination.tsx +17 -31
  49. package/src/components/table/table.tsx +2 -2
  50. package/src/components/version/version.tsx +1 -1
  51. package/src/components/vulnerability/constants.ts +6 -6
  52. package/src/components/vulnerability/severityPill.tsx +6 -7
  53. package/src/globals.css +12 -1
  54. package/src/hooks/index.ts +1 -0
  55. package/src/hooks/useClickOutside.ts +5 -22
  56. package/src/icons/icons.tsx +431 -0
  57. package/src/icons/index.ts +2 -0
  58. package/src/index.ts +0 -1
  59. package/src/types/hooks.d.ts +8 -2
  60. package/src/utils/index.ts +1 -1
  61. package/src/utils/logoConsoleOutput/logoConsoleOutput.ts +31 -0
  62. package/tsconfig.json +3 -2
  63. package/tsdown.config.ts +26 -0
  64. package/Readme.md +0 -29
  65. package/bun.lock +0 -662
  66. package/dist/src/components/alert/alert.d.ts +0 -8
  67. package/dist/src/components/alert/alert.js +0 -15
  68. package/dist/src/components/buttons/button.d.ts +0 -13
  69. package/dist/src/components/buttons/button.js +0 -30
  70. package/dist/src/components/confirm/confirmPopup.d.ts +0 -12
  71. package/dist/src/components/confirm/confirmPopup.js +0 -18
  72. package/dist/src/components/container/highlight.d.ts +0 -4
  73. package/dist/src/components/container/highlight.js +0 -4
  74. package/dist/src/components/container/page.d.ts +0 -8
  75. package/dist/src/components/container/page.js +0 -4
  76. package/dist/src/components/inputs/checkbox.d.ts +0 -17
  77. package/dist/src/components/inputs/checkbox.js +0 -37
  78. package/dist/src/components/inputs/input.d.ts +0 -12
  79. package/dist/src/components/inputs/input.js +0 -124
  80. package/dist/src/components/inputs/radio.d.ts +0 -17
  81. package/dist/src/components/inputs/radio.js +0 -27
  82. package/dist/src/components/inputs/range.d.ts +0 -11
  83. package/dist/src/components/inputs/range.js +0 -22
  84. package/dist/src/components/inputs/searchInput.d.ts +0 -6
  85. package/dist/src/components/inputs/searchInput.js +0 -42
  86. package/dist/src/components/inputs/select.d.ts +0 -23
  87. package/dist/src/components/inputs/select.js +0 -70
  88. package/dist/src/components/inputs/shared/colorPickerPopup.d.ts +0 -8
  89. package/dist/src/components/inputs/shared/colorPickerPopup.js +0 -191
  90. package/dist/src/components/inputs/shared/dateTimePickerPopup.d.ts +0 -9
  91. package/dist/src/components/inputs/shared/dateTimePickerPopup.js +0 -128
  92. package/dist/src/components/inputs/shared/fieldWrapper.d.ts +0 -14
  93. package/dist/src/components/inputs/shared/fieldWrapper.js +0 -8
  94. package/dist/src/components/inputs/shared/index.d.ts +0 -5
  95. package/dist/src/components/inputs/shared/index.js +0 -5
  96. package/dist/src/components/inputs/shared/inputError.d.ts +0 -6
  97. package/dist/src/components/inputs/shared/inputError.js +0 -6
  98. package/dist/src/components/inputs/shared/inputInfo.d.ts +0 -5
  99. package/dist/src/components/inputs/shared/inputInfo.js +0 -5
  100. package/dist/src/components/inputs/shared/inputLabel.d.ts +0 -9
  101. package/dist/src/components/inputs/shared/inputLabel.js +0 -4
  102. package/dist/src/components/inputs/switch.d.ts +0 -11
  103. package/dist/src/components/inputs/switch.js +0 -14
  104. package/dist/src/components/inputs/tagInput.d.ts +0 -15
  105. package/dist/src/components/inputs/tagInput.js +0 -40
  106. package/dist/src/components/inputs/textarea.d.ts +0 -11
  107. package/dist/src/components/inputs/textarea.js +0 -36
  108. package/dist/src/components/inputs/toggle.d.ts +0 -14
  109. package/dist/src/components/inputs/toggle.js +0 -12
  110. package/dist/src/components/login/loginPage.d.ts +0 -2
  111. package/dist/src/components/login/loginPage.js +0 -18
  112. package/dist/src/components/logo/logo.d.ts +0 -5
  113. package/dist/src/components/logo/logo.js +0 -6
  114. package/dist/src/components/logo/logoSmall.d.ts +0 -1
  115. package/dist/src/components/logo/logoSmall.js +0 -6
  116. package/dist/src/components/markdownrender/markdownRender.d.ts +0 -6
  117. package/dist/src/components/markdownrender/markdownRender.js +0 -25
  118. package/dist/src/components/navbar/bubble.d.ts +0 -14
  119. package/dist/src/components/navbar/bubble.js +0 -8
  120. package/dist/src/components/navbar/navbar.d.ts +0 -34
  121. package/dist/src/components/navbar/navbar.js +0 -36
  122. package/dist/src/components/navbar/navbarDropdown.d.ts +0 -7
  123. package/dist/src/components/navbar/navbarDropdown.js +0 -19
  124. package/dist/src/components/navbar/navbarItem.d.ts +0 -11
  125. package/dist/src/components/navbar/navbarItem.js +0 -12
  126. package/dist/src/components/table/body.d.ts +0 -15
  127. package/dist/src/components/table/body.js +0 -116
  128. package/dist/src/components/table/format.d.ts +0 -1
  129. package/dist/src/components/table/format.js +0 -27
  130. package/dist/src/components/table/header.d.ts +0 -8
  131. package/dist/src/components/table/header.js +0 -47
  132. package/dist/src/components/table/menu.d.ts +0 -17
  133. package/dist/src/components/table/menu.js +0 -30
  134. package/dist/src/components/table/pagination.d.ts +0 -6
  135. package/dist/src/components/table/pagination.js +0 -86
  136. package/dist/src/components/table/table.d.ts +0 -14
  137. package/dist/src/components/table/table.js +0 -14
  138. package/dist/src/components/toast/toastItem.d.ts +0 -2
  139. package/dist/src/components/toast/toastItem.js +0 -38
  140. package/dist/src/components/toast/toaster.d.ts +0 -9
  141. package/dist/src/components/toast/toaster.js +0 -86
  142. package/dist/src/components/toggle/language.d.ts +0 -4
  143. package/dist/src/components/toggle/language.js +0 -29
  144. package/dist/src/components/toggle/theme.d.ts +0 -3
  145. package/dist/src/components/toggle/theme.js +0 -30
  146. package/dist/src/components/version/version.d.ts +0 -7
  147. package/dist/src/components/version/version.js +0 -12
  148. package/dist/src/components/vulnerability/constants.d.ts +0 -4
  149. package/dist/src/components/vulnerability/constants.js +0 -14
  150. package/dist/src/components/vulnerability/severityPill.d.ts +0 -8
  151. package/dist/src/components/vulnerability/severityPill.js +0 -8
  152. package/dist/src/hooks/useClickOutside.d.ts +0 -2
  153. package/dist/src/hooks/useClickOutside.js +0 -27
  154. package/dist/src/hooks/useDarkMode.d.ts +0 -1
  155. package/dist/src/hooks/useDarkMode.js +0 -17
  156. package/dist/src/hooks/useVisibility.d.ts +0 -4
  157. package/dist/src/hooks/useVisibility.js +0 -23
  158. package/dist/src/index.d.ts +0 -5
  159. package/dist/src/index.js +0 -5
  160. package/dist/src/scripts/buildCss.d.ts +0 -1
  161. package/dist/src/scripts/buildCss.js +0 -22
  162. package/dist/src/scripts/index.d.ts +0 -1
  163. package/dist/src/scripts/index.js +0 -1
  164. package/dist/src/scripts/rewriteAlias.d.ts +0 -1
  165. package/dist/src/scripts/rewriteAlias.js +0 -31
  166. package/dist/src/utils/LogoConsoleOutput/LogoConsoleOutput.d.ts +0 -1
  167. package/dist/src/utils/LogoConsoleOutput/LogoConsoleOutput.js +0 -44
  168. package/dist/src/utils/auth/callback.d.ts +0 -2
  169. package/dist/src/utils/auth/callback.js +0 -63
  170. package/dist/src/utils/auth/getDomain.d.ts +0 -2
  171. package/dist/src/utils/auth/getDomain.js +0 -6
  172. package/dist/src/utils/auth/login.d.ts +0 -3
  173. package/dist/src/utils/auth/login.js +0 -14
  174. package/dist/src/utils/auth/logout.d.ts +0 -3
  175. package/dist/src/utils/auth/logout.js +0 -18
  176. package/dist/src/utils/auth/token.d.ts +0 -3
  177. package/dist/src/utils/auth/token.js +0 -33
  178. package/dist/tailwind.config.d.ts +0 -3
  179. package/dist/tailwind.config.js +0 -7
  180. package/src/scripts/buildCss.ts +0 -25
  181. package/src/scripts/index.ts +0 -1
  182. package/src/scripts/rewriteAlias.ts +0 -39
  183. package/src/utils/LogoConsoleOutput/LogoConsoleOutput.ts +0 -61
@@ -0,0 +1,124 @@
1
+ import type { ReactNode } from 'react'
2
+ import VersionTag from '@components/version/version'
3
+
4
+ export type Lang = 'no' | 'en'
5
+ export type BilingualString = string | { no: string; en: string }
6
+
7
+ export type FooterColumn = {
8
+ heading: BilingualString
9
+ items: { label: BilingualString; href?: string }[]
10
+ }
11
+
12
+ export type FooterSocialLink = {
13
+ title: string
14
+ href: string
15
+ icon: ReactNode
16
+ hoverClass?: string
17
+ }
18
+
19
+ export type FooterProps = {
20
+ logo: ReactNode
21
+ sponsor?: { node: ReactNode; label?: BilingualString }
22
+ columns?: FooterColumn[]
23
+ socialLinks?: FooterSocialLink[]
24
+ copyright: BilingualString
25
+ version?: { tag: string; href: string }
26
+ lang?: Lang
27
+ className?: string
28
+ }
29
+
30
+ function t(s: BilingualString, lang: Lang): string {
31
+ return typeof s === 'string' ? s : s[lang]
32
+ }
33
+
34
+ export default function Footer({
35
+ logo,
36
+ sponsor,
37
+ columns,
38
+ socialLinks,
39
+ copyright,
40
+ version,
41
+ lang = 'no',
42
+ className,
43
+ }: FooterProps) {
44
+ const year = new Date().getFullYear()
45
+ const hasRight = (columns && columns.length > 0) || (socialLinks && socialLinks.length > 0)
46
+
47
+ return (
48
+ <div className={`mt-24 bg-login-950 md:mt-40 ${className || ''}`}>
49
+ <div className={`
50
+ mx-auto w-full px-4 pt-16 pb-24 md:max-w-304 md:px-12 md:pt-20 md:pb-4
51
+ ${hasRight ? 'md:grid md:grid-cols-[18rem_1fr] md:gap-x-12' : ''}
52
+ `}>
53
+ <div className='mx-auto grid w-full max-w-60 gap-16 md:row-span-2 md:max-w-72 md:gap-20'>
54
+ <div className='block w-full'>{logo}</div>
55
+ {sponsor && (
56
+ <div>
57
+ <div className='block w-full'>{sponsor.node}</div>
58
+ {sponsor.label && (
59
+ <p className='pt-8 text-center text-login-100'>{t(sponsor.label, lang)}</p>
60
+ )}
61
+ </div>
62
+ )}
63
+ </div>
64
+
65
+ {columns && columns.length > 0 && (
66
+ <div className={`
67
+ mt-12 grid w-full max-w-60 gap-8
68
+ sm:max-w-88 sm:justify-items-end sm:justify-self-end
69
+ md:col-start-2 md:row-start-1 md:mt-0 md:max-w-136 md:gap-0 md:justify-self-end
70
+ ${columns.length > 1 ? 'sm:grid-cols-2' : ''}
71
+ `}>
72
+ {columns.map((col, i) => (
73
+ <div key={i} className='sm:justify-self-center md:justify-self-end'>
74
+ <h4 className='pb-2 text-sm font-medium tracking-widest text-login-100'>
75
+ {t(col.heading, lang)}
76
+ </h4>
77
+ {col.items.map((item, j) =>
78
+ item.href ? (
79
+ <a key={j} className='link--underscore-hover block' href={item.href}>
80
+ {t(item.label, lang)}
81
+ </a>
82
+ ) : (
83
+ <p key={j}>{t(item.label, lang)}</p>
84
+ )
85
+ )}
86
+ </div>
87
+ ))}
88
+ </div>
89
+ )}
90
+
91
+ {socialLinks && socialLinks.length > 0 && (
92
+ <div className='md:col-start-2 md:row-start-2 md:justify-self-end'>
93
+ <div className='mx-auto mt-20 mb-12 flex w-fit flex-wrap justify-center gap-6'>
94
+ {socialLinks.map((link, i) => (
95
+ <a
96
+ key={i}
97
+ className={`block size-8 text-login-100 transition-all duration-200 group
98
+ ${link.hoverClass ?? 'hover:text-login-50'}`}
99
+ title={link.title}
100
+ href={link.href}
101
+ target='_blank'
102
+ rel='noreferrer'
103
+ >
104
+ {link.icon}
105
+ </a>
106
+ ))}
107
+ </div>
108
+ </div>
109
+ )}
110
+
111
+ <div className='mt-16 flex flex-col items-start gap-4 md:col-span-2 md:row-start-3 md:mt-24 md:grid
112
+ md:grid-cols-[auto_min-content] md:items-end md:gap-8'
113
+ >
114
+ <p className='text-xs wrap-break-word text-login-100'>
115
+ Copyright © {year} {t(copyright, lang)}
116
+ </p>
117
+ {version && (
118
+ <VersionTag version={version.tag} url={version.href} />
119
+ )}
120
+ </div>
121
+ </div>
122
+ </div>
123
+ )
124
+ }
@@ -0,0 +1,70 @@
1
+ import React from 'react'
2
+ import { Discord, Facebook, Linkedin, Github, Wikijs } from '../../icons/icons'
3
+ import type { LogfontIconProps } from '../../icons/icons'
4
+ import type { FooterColumn, FooterSocialLink } from './footer'
5
+
6
+ export type { FooterSocialLink as LoginSocialLinkData }
7
+
8
+ // eslint-disable-next-line @stylistic/max-len
9
+ const INSTAGRAM_PATH = 'M512 960Q408 960 375.0 959.5Q342 959 301 957Q260 955 230.5 949.0Q201 943 177 933Q151 923 129.0 909.0Q107 895 86 874Q65 853 51.0 831.0Q37 809 27 783Q17 759 11.0 729.5Q5 700 3 659Q1 618 0.5 585.0Q0 552 0 448Q0 344 0.5 311.0Q1 278 3 237Q5 196 11.0 166.5Q17 137 27 113Q37 87 51.0 65.0Q65 43 86 22Q107 1 129.0 -13.0Q151 -27 177 -37Q201 -47 230.5 -53.0Q260 -59 301 -61Q342 -63 375.0 -63.5Q408 -64 512 -64Q616 -64 649.0 -63.5Q682 -63 723 -61Q764 -59 793.5 -53.0Q823 -47 847 -37Q873 -27 895.0 -13.0Q917 1 938 22Q959 43 973.0 65.0Q987 87 997 113Q1007 137 1013.0 166.5Q1019 196 1021 237Q1023 278 1023.5 311.0Q1024 344 1024 448Q1024 552 1023.5 585.0Q1023 618 1021 659Q1019 700 1013.0 729.5Q1007 759 997 783Q987 809 973.0 831.0Q959 853 938 874Q917 895 895.0 909.0Q873 923 847 933Q823 943 793.5 949.0Q764 955 723 957Q682 959 649.0 959.5Q616 960 512 960ZM512 868Q614 868 646.0 867.5Q678 867 719 865Q756 863 778.0 857.5Q800 852 814 847Q832 840 845.5 831.0Q859 822 873 809Q886 795 895.0 781.5Q904 768 911 750Q916 736 921.5 714.0Q927 692 929 655Q931 614 931.5 582.5Q932 551 932 448Q932 345 931.0 313.5Q930 282 929 241Q927 204 921.5 182.0Q916 160 911 146Q903 128 894.5 114.5Q886 101 872 87Q859 74 845.0 65.0Q831 56 813 49Q800 44 777.5 38.5Q755 33 718 31Q677 29 645.0 28.5Q613 28 511 28Q408 28 376.0 29.0Q344 30 303 31Q266 33 243.5 38.5Q221 44 208 49Q190 57 176.5 65.5Q163 74 149 88Q136 101 126.5 115.0Q117 129 111 147Q105 160 100.0 182.5Q95 205 93 242Q91 282 90.5 314.0Q90 346 90 449Q90 551 90.5 583.0Q91 615 93 656Q95 693 100.0 715.5Q105 738 111 751Q117 770 126.5 783.5Q136 797 149 810Q163 824 176.5 833.0Q190 842 208 849Q221 854 243.0 859.5Q265 865 303 867Q344 868 375.5 868.5Q407 869 510 869ZM512 711Q458 711 410 690Q362 670 326.0 634.0Q290 598 270 550Q249 502 249 448Q249 394 270 346Q290 298 326.0 262.0Q362 226 410 206Q458 185 512 185Q566 185 614 206Q662 226 698.0 262.0Q734 298 754 346Q775 394 775 448Q775 502 754 550Q734 598 698.0 634.0Q662 670 614 690Q566 711 512 711ZM512 277Q441 277 391.0 327.0Q341 377 341 448Q341 519 391.0 569.0Q441 619 512 619Q583 619 633.0 569.0Q683 519 683 448Q683 377 633.0 327.0Q583 277 512 277ZM847 721Q847 696 829.0 678.0Q811 660 785 660Q760 660 742.0 678.0Q724 696 724 721Q724 747 742.0 765.0Q760 783 785 783Q811 783 829.0 765.0Q847 747 847 721Z'
10
+
11
+ function InstagramIcon({ size = 24 }: LogfontIconProps) {
12
+ return (
13
+ <span className='relative block' style={{ width: size, height: size }}>
14
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'
15
+ width={size} height={size} aria-hidden='true'
16
+ className='absolute inset-0 transition-opacity duration-200 opacity-100 group-hover:opacity-0'>
17
+ <g transform='translate(0 960) scale(1 -1)'>
18
+ <path fill='currentColor' d={INSTAGRAM_PATH} />
19
+ </g>
20
+ </svg>
21
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1024 1024'
22
+ width={size} height={size} aria-hidden='true'
23
+ className='absolute inset-0 transition-opacity duration-200 opacity-0 group-hover:opacity-100'>
24
+ <defs>
25
+ <linearGradient id='ig-grad' x1='0%' y1='100%' x2='100%' y2='0%'>
26
+ <stop offset='0%' stopColor='#fff695' />
27
+ <stop offset='45%' stopColor='#ff5445' />
28
+ <stop offset='60%' stopColor='#ff37c0' />
29
+ <stop offset='90%' stopColor='#3d6dff' />
30
+ </linearGradient>
31
+ </defs>
32
+ <g transform='translate(0 960) scale(1 -1)'>
33
+ <path fill='url(#ig-grad)' d={INSTAGRAM_PATH} />
34
+ </g>
35
+ </svg>
36
+ </span>
37
+ )
38
+ }
39
+
40
+ export const loginAddress: FooterColumn = {
41
+ heading: { no: 'Adresse', en: 'Address' },
42
+ items: [
43
+ { label: 'Teknologivegen 22' },
44
+ { label: { no: 'Bygg A, rom 155', en: 'Building A, room 155' } },
45
+ { label: '2815 GJØVIK' },
46
+ ],
47
+ }
48
+
49
+ export const loginEmail = (email: string): FooterColumn => ({
50
+ heading: { no: 'E-post', en: 'Email' },
51
+ items: [{ label: email, href: `mailto:${email}` }],
52
+ })
53
+
54
+ export const loginCopyright = {
55
+ no: 'Login - Linjeforeningen for IT, NO 811 940 372',
56
+ en: 'Login - Association for IT, NO 811 940 372',
57
+ } as const
58
+
59
+ export const loginSponsor = {
60
+ label: { no: 'Hovedsamarbeidspartner', en: 'Main partner' },
61
+ } as const
62
+
63
+ export const loginSocialLinks: FooterSocialLink[] = [
64
+ { title: 'Discord', href: 'https://discord.gg/login-ntnu', icon: <Discord size={24} />, hoverClass: 'hover:text-[#6571fd]' },
65
+ { title: 'Instagram', href: 'https://www.instagram.com/login_linjeforening/', icon: <InstagramIcon size={24} />, },
66
+ { title: 'Facebook', href: 'https://facebook.com/LogNTNU', icon: <Facebook size={24} />, hoverClass: 'hover:text-[#2c87ff]' },
67
+ { title: 'LinkedIn', href: 'https://www.linkedin.com/company/linjeforeningen-login/about', icon: <Linkedin size={24} />, hoverClass: 'hover:text-[#1a7bdd]' },
68
+ { title: 'GitHub', href: 'https://github.com/login-linjeforeningen-for-it', icon: <Github size={24} />, hoverClass: 'hover:text-white' },
69
+ { title: 'Wiki', href: 'https://outline.login.no/s/doc', icon: <Wikijs size={24} />, hoverClass: 'hover:text-login-50' },
70
+ ]
@@ -1,3 +1,4 @@
1
+ 'use client'
1
2
  // Input components
2
3
  export { default as Input } from './inputs/input'
3
4
  export { default as SearchInput } from './inputs/searchInput'
@@ -5,6 +6,7 @@ export { default as Textarea } from './inputs/textarea'
5
6
  export { default as Checkbox } from './inputs/checkbox'
6
7
  export { default as Select } from './inputs/select'
7
8
  export { default as TagInput } from './inputs/tagInput'
9
+ export { default as MultiSelect } from './inputs/multiSelect'
8
10
  export { default as Switch } from './inputs/switch'
9
11
  export { default as Radio } from './inputs/radio'
10
12
  export { default as Range } from './inputs/range'
@@ -22,9 +24,22 @@ export { default as Navbar } from './navbar/navbar'
22
24
  export { default as NavItem } from './navbar/navbarItem'
23
25
  export { default as NavDropdown } from './navbar/navbarDropdown'
24
26
 
27
+ // Footer
28
+ export { default as Footer } from './footer/footer'
29
+ export type { FooterColumn, FooterSocialLink, FooterProps, Lang, BilingualString } from './footer/footer'
30
+ export { loginAddress, loginEmail, loginCopyright, loginSponsor, loginSocialLinks } from './footer/loginContent'
31
+ export type { LoginSocialLinkData } from './footer/loginContent'
32
+
25
33
  // Containers
26
34
  export { default as PageContainer} from './container/page'
27
35
  export { default as Highlight } from './container/highlight'
36
+ export { default as LeftBarPanel } from './container/leftBarPanel'
37
+ export { default as GlassCard } from './container/glassCard'
38
+ export { default as IconBubble } from './container/iconBubble'
39
+ export { default as StatCard } from './container/statCard'
40
+ export { Tabs, TabPanel } from './container/tabs'
41
+ export { default as Accordion, AccordionGroup } from './container/accordion'
42
+ export { default as PulseDot } from './container/pulseDot'
28
43
 
29
44
  // Login
30
45
  export { default as VersionTag } from './version/version'
@@ -87,11 +87,11 @@ function CheckboxItem(props: CheckboxItemProps) {
87
87
  className={`
88
88
  peer appearance-none rounded border border-login-500 bg-login-500/50
89
89
  checked:bg-login checked:border-login
90
- focus:outline-none focus:ring-2 focus:ring-login/50
91
- disabled:opacity-50 disabled:cursor-not-allowed
92
- cursor-pointer transition-all duration-200
90
+ focus:outline-none focus:ring-1 focus:ring-login/30
91
+ disabled:opacity-40 disabled:cursor-not-allowed
92
+ cursor-pointer transition-all duration-150
93
93
  w-5 h-5 absolute inset-0
94
- ${error ? 'border-red-500' : ''}
94
+ ${error ? 'border-red-500/70' : ''}
95
95
  `}
96
96
  />
97
97
  <Check
@@ -24,7 +24,8 @@ export default function Input(props: InputProps) {
24
24
  const id = useId()
25
25
  const anchorName = `--input-${id.replace(/:/g, '')}`
26
26
 
27
- const containerRef = useClickOutside<HTMLDivElement>(() => setIsOpen(false))
27
+ const containerRef = useRef<HTMLDivElement>(null)
28
+ useClickOutside(containerRef, () => setIsOpen(false))
28
29
 
29
30
  const isDateType = ['date', 'datetime-local', 'time'].includes(type as string)
30
31
  const isColorType = type === 'color'
@@ -38,11 +39,12 @@ export default function Input(props: InputProps) {
38
39
  }
39
40
  }
40
41
 
42
+ function pad(n: number) { return n.toString().padStart(2, '0') }
43
+
41
44
  function handleDateChange(date: Date) {
42
45
  const onChange = inputProps.onChange
43
46
  if (!onChange) return
44
47
 
45
- const pad = (n: number) => n.toString().padStart(2, '0')
46
48
  const yyyy = date.getFullYear()
47
49
  const MM = pad(date.getMonth() + 1)
48
50
  const dd = pad(date.getDate())
@@ -110,10 +112,6 @@ export default function Input(props: InputProps) {
110
112
  const date = getDateValue()
111
113
  if (!date) return value as string
112
114
 
113
- function pad(n: number) {
114
- return n.toString().padStart(2, '0')
115
- }
116
-
117
115
  const yyyy = date.getFullYear()
118
116
  const MM = pad(date.getMonth() + 1)
119
117
  const dd = pad(date.getDate())
@@ -162,14 +160,14 @@ export default function Input(props: InputProps) {
162
160
  aria-describedby={error ? `${name}-error` : undefined}
163
161
  style={{ anchorName } as any}
164
162
  className={`
165
- w-full rounded-md bg-login-500/50 border border-login-500
166
- text-login-text placeholder-login-200
167
- focus:outline-none focus:border-login focus:ring-1 focus:ring-login
168
- disabled:opacity-50 disabled:cursor-not-allowed
169
- h-10.5 py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
170
- transition-all duration-200
163
+ w-full rounded-md bg-login-500/50 border border-login-500
164
+ text-login-text placeholder-login-200 text-sm
165
+ focus:outline-none focus:border-login/60 focus:ring-1 focus:ring-login/30
166
+ disabled:opacity-40 disabled:cursor-not-allowed
167
+ h-10 py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
168
+ transition-all duration-150
171
169
  input-reset
172
- ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
170
+ ${error ? 'border-red-500/70 focus:border-red-500/70 focus:ring-red-500/30' : ''}
173
171
  ${isClickableType && !inputProps.disabled ? 'cursor-pointer' : ''}
174
172
  `}
175
173
  />
@@ -0,0 +1,144 @@
1
+ 'use client'
2
+
3
+ import { useState, useRef } from 'react'
4
+ import { X, ChevronDown, Check } from 'lucide-react'
5
+ import { FieldWrapper } from './shared'
6
+ import useClickOutside from '../../hooks/useClickOutside'
7
+
8
+ type Option = {
9
+ label: string
10
+ value: string
11
+ }
12
+
13
+ export type MultiSelectProps = {
14
+ label?: string
15
+ name: string
16
+ options: Option[]
17
+ value?: string[]
18
+ onChange?: (value: string[]) => void
19
+ placeholder?: string
20
+ error?: string
21
+ className?: string
22
+ disabled?: boolean
23
+ required?: boolean
24
+ info?: string
25
+ description?: string
26
+ textSize?: 'sm' | 'md'
27
+ }
28
+
29
+ export default function MultiSelect({
30
+ label,
31
+ name,
32
+ options,
33
+ value = [],
34
+ onChange,
35
+ placeholder = 'Select…',
36
+ error,
37
+ className,
38
+ disabled,
39
+ required,
40
+ info,
41
+ description,
42
+ textSize = 'sm',
43
+ }: MultiSelectProps) {
44
+ const [open, setOpen] = useState(false)
45
+ const containerRef = useRef<HTMLDivElement>(null)
46
+ useClickOutside(containerRef, () => setOpen(false))
47
+
48
+ function toggleOption(optionValue: string) {
49
+ if (!onChange) return
50
+ if (value.includes(optionValue)) {
51
+ onChange(value.filter((v) => v !== optionValue))
52
+ } else {
53
+ onChange([...value, optionValue])
54
+ }
55
+ }
56
+
57
+ function removeOption(optionValue: string) {
58
+ if (!onChange) return
59
+ onChange(value.filter((v) => v !== optionValue))
60
+ }
61
+
62
+
63
+ return (
64
+ <FieldWrapper
65
+ label={label}
66
+ name={name}
67
+ required={required}
68
+ info={info}
69
+ description={description}
70
+ error={error}
71
+ className={className}
72
+ textSize={textSize}
73
+ >
74
+ <div ref={containerRef} className='relative w-full'>
75
+ <div
76
+ onClick={() => !disabled && setOpen((o) => !o)}
77
+ className={`
78
+ flex min-h-10 cursor-pointer items-center justify-between gap-2
79
+ rounded-md bg-login-500/50 border border-login-500 px-3 py-1.5
80
+ transition-all duration-150 select-none
81
+ ${open ? 'border-login/60 ring-1 ring-login/30' : ''}
82
+ ${error ? 'border-red-500/70' : ''}
83
+ ${disabled ? 'opacity-40 cursor-not-allowed' : ''}
84
+ `}
85
+ >
86
+ <div className='flex flex-wrap gap-1.5'>
87
+ {value.length === 0 && (
88
+ <span className='text-sm text-login-200'>{placeholder}</span>
89
+ )}
90
+ {value.map((val) => {
91
+ const option = options.find((o) => o.value === val)
92
+ if (!option) return null
93
+ return (
94
+ <span
95
+ key={val}
96
+ className='flex items-center gap-1 px-2 py-0.5 bg-login-600 text-login-100 border border-login-500 rounded text-sm'
97
+ >
98
+ {option.label}
99
+ {!disabled && (
100
+ <button
101
+ type='button'
102
+ onClick={(e) => { e.stopPropagation(); removeOption(val) }}
103
+ className='hover:text-red-300 transition-colors'
104
+ >
105
+ <X size={13} />
106
+ </button>
107
+ )}
108
+ </span>
109
+ )
110
+ })}
111
+ </div>
112
+ <ChevronDown
113
+ size={16}
114
+ className={`shrink-0 text-login-200 transition-transform duration-150 ${open ? 'rotate-180' : ''}`}
115
+ />
116
+ </div>
117
+
118
+ {open && options.length > 0 && (
119
+ <div className='absolute z-50 mt-1 w-full max-h-60 overflow-auto rounded-md bg-login-800 border border-login-500/50 shadow-lg'>
120
+ {options.map((option) => {
121
+ const selected = value.includes(option.value)
122
+ return (
123
+ <div
124
+ key={option.value}
125
+ onClick={() => toggleOption(option.value)}
126
+ className={`
127
+ flex cursor-pointer items-center justify-between px-3 py-2 text-sm
128
+ hover:bg-login-700 transition-colors select-none
129
+ ${selected ? 'text-login-50' : 'text-login-100'}
130
+ `}
131
+ >
132
+ <span>{option.label}</span>
133
+ {selected && <Check size={14} className='text-login shrink-0' />}
134
+ </div>
135
+ )
136
+ })}
137
+ </div>
138
+ )}
139
+
140
+ <input type='hidden' name={name} value={value.join(',')} />
141
+ </div>
142
+ </FieldWrapper>
143
+ )
144
+ }
@@ -78,11 +78,11 @@ function RadioItem(props: RadioItemProps) {
78
78
  className={`
79
79
  peer appearance-none rounded-full border border-login-500 bg-login-500/50
80
80
  checked:bg-login checked:border-login
81
- focus:outline-none focus:ring-2 focus:ring-login/50
82
- disabled:opacity-50 disabled:cursor-not-allowed
83
- cursor-pointer transition-all duration-200
81
+ focus:outline-none focus:ring-1 focus:ring-login/30
82
+ disabled:opacity-40 disabled:cursor-not-allowed
83
+ cursor-pointer transition-all duration-150
84
84
  w-5 h-5 absolute inset-0
85
- ${error ? 'border-red-500' : ''}
85
+ ${error ? 'border-red-500/70' : ''}
86
86
  `}
87
87
  />
88
88
  <div className={`
@@ -13,13 +13,14 @@ export type RangeProps = Omit<React.ComponentProps<'input'>, 'name'> & {
13
13
 
14
14
  export default function Range(props: RangeProps) {
15
15
  const { name, label, error, className, info, description, showValue = true, textSize = 'sm', ...inputProps } = props
16
- const { min = 0, max = 100, step = 1, value = 0 } = inputProps
16
+ const { min = 0, max = 100, step = 1, value, defaultValue, onChange, ...restInputProps } = inputProps
17
+ const displayValue = value ?? defaultValue ?? 0
17
18
 
18
19
  return (
19
20
  <FieldWrapper
20
21
  label={label}
21
22
  name={name}
22
- required={inputProps.required}
23
+ required={restInputProps.required}
23
24
  info={info}
24
25
  description={description}
25
26
  error={error}
@@ -28,14 +29,14 @@ export default function Range(props: RangeProps) {
28
29
  >
29
30
  <div className='flex items-center gap-4'>
30
31
  <input
31
- {...inputProps}
32
+ {...restInputProps}
32
33
  id={name}
33
34
  name={name}
34
35
  type='range'
35
36
  min={min}
36
37
  max={max}
37
38
  step={step}
38
- value={value}
39
+ {...(onChange ? { value, onChange } : { defaultValue: value ?? defaultValue ?? 0 })}
39
40
  title={label}
40
41
  aria-invalid={!!error}
41
42
  aria-describedby={error ? `${name}-error` : undefined}
@@ -58,7 +59,7 @@ export default function Range(props: RangeProps) {
58
59
  />
59
60
  {showValue && (
60
61
  <span className='text-login-text text-sm font-medium min-w-10 text-right'>
61
- {value}
62
+ {displayValue}
62
63
  </span>
63
64
  )}
64
65
  </div>
@@ -59,7 +59,7 @@ export default function SearchInput({ placeholder = 'Search...', variant = 'defa
59
59
  </div>
60
60
  )
61
61
  } else {
62
- <Input
62
+ return <Input
63
63
  name='search'
64
64
  icon={<Search className='w-5 h-5' />}
65
65
  value={searchValue}
@@ -1,8 +1,8 @@
1
1
  'use client'
2
2
 
3
- import { useState, useEffect } from 'react'
3
+ import { useState, useEffect, useRef } from 'react'
4
4
  import Image from 'next/image'
5
- import { useClickOutside } from '../../hooks'
5
+ import useClickOutside from '../../hooks/useClickOutside'
6
6
  import { ChevronDown, X, Search } from 'lucide-react'
7
7
  import { FieldWrapper } from './shared'
8
8
 
@@ -63,7 +63,8 @@ export default function Select({
63
63
  setSelectedOption(options.find(opt => opt.value === value))
64
64
  }, [value, options])
65
65
 
66
- const containerRef = useClickOutside<HTMLDivElement>(() => setIsOpen(false))
66
+ const containerRef = useRef<HTMLDivElement>(null)
67
+ useClickOutside(containerRef, () => setIsOpen(false))
67
68
 
68
69
  function handleSelect(option: Option) {
69
70
  if (disabled) return
@@ -107,14 +108,14 @@ export default function Select({
107
108
  aria-expanded={isOpen}
108
109
  aria-labelledby={label ? undefined : name}
109
110
  className={`
110
- w-full rounded-md bg-login-500/50 border border-login-500
111
- text-login-text text-left
112
- focus:outline-none focus:border-login focus:ring-1 focus:ring-login
113
- disabled:opacity-50 disabled:cursor-not-allowed
114
- h-10.5 py-2 pl-3 pr-10
115
- transition-all duration-200
111
+ w-full rounded-md bg-login-500/50 border border-login-500
112
+ text-login-text text-left text-sm
113
+ focus:outline-none focus:border-login/60 focus:ring-1 focus:ring-login/30
114
+ disabled:opacity-40 disabled:cursor-not-allowed
115
+ h-10 py-2 pl-3 pr-10
116
+ transition-all duration-150
116
117
  flex items-center justify-between
117
- ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
118
+ ${error ? 'border-red-500/70 focus:border-red-500/70 focus:ring-red-500/30' : ''}
118
119
  ${!selectedOption ? 'text-login-200' : ''}
119
120
  `}
120
121
  title={label}
@@ -159,11 +160,11 @@ export default function Select({
159
160
 
160
161
  {isOpen && (
161
162
  <div className={`
162
- absolute z-50 w-full mt-1 bg-login-600 border border-login-500
163
- rounded-md shadow-lg max-h-60 overflow-hidden flex flex-col
163
+ absolute z-50 w-full mt-1 bg-login-700 border border-login-500/50
164
+ rounded-md shadow-xl max-h-60 overflow-hidden flex flex-col
164
165
  `}>
165
166
  {searchable && (
166
- <div className='p-2 sticky top-0 bg-login-600 border-b border-login-500 z-10'>
167
+ <div className='p-2 sticky top-0 bg-login-700 border-b border-login-500/20 z-10'>
167
168
  <div className='relative'>
168
169
  <Search className='absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-login-200' />
169
170
  <input
@@ -173,9 +174,9 @@ export default function Select({
173
174
  placeholder='Search...'
174
175
  autoFocus
175
176
  className={`
176
- w-full bg-login-500/50 border border-login-500 rounded-md
177
- py-1.5 pl-9 pr-3 text-sm text-login-text
178
- focus:outline-none focus:border-login focus:ring-1 focus:ring-login
177
+ w-full bg-login-600 border border-login-500/50 rounded-md
178
+ py-1.5 pl-9 pr-3 text-sm text-login-text
179
+ focus:outline-none focus:border-login/60 focus:ring-1 focus:ring-login/30
179
180
  `}
180
181
  />
181
182
  </div>
@@ -191,9 +192,9 @@ export default function Select({
191
192
  onClick={() => handleSelect(option)}
192
193
  className={`
193
194
  w-full text-left px-3 py-2 text-sm
194
- hover:bg-login-500 transition-colors duration-150
195
+ hover:bg-login-600 transition-colors duration-150
195
196
  flex items-center gap-2
196
- ${selectedOption?.value === option.value ? 'bg-login-500 text-login': 'text-login-text'}
197
+ ${selectedOption?.value === option.value ? 'bg-login/15 text-login' : 'text-login-text'}
197
198
  `}
198
199
  >
199
200
  {option.image && (
@@ -193,7 +193,7 @@ export default function ColorPickerPopup({ value, onChange, onClose, anchorName
193
193
 
194
194
  return (
195
195
  <div
196
- className='fixed z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-3 w-64 select-none anchor-popup'
196
+ className='fixed z-50 bg-login-700 border border-login-500/50 rounded-md shadow-xl p-3 w-64 select-none anchor-popup'
197
197
  style={{
198
198
  positionAnchor: anchorName,
199
199
  positionArea: 'bottom span-right',
@@ -212,19 +212,19 @@ export default function ColorPickerPopup({ value, onChange, onClose, anchorName
212
212
  value={hexInput}
213
213
  onChange={manualHexChange}
214
214
  className={`
215
- flex-1 min-w-0 bg-login-500 border border-login-500 rounded
215
+ flex-1 min-w-0 bg-login-600 border border-login-500/50 rounded
216
216
  px-2 py-1 text-sm text-login-text focus:outline-none
217
- focus:border-login focus:ring-1 focus:ring-login
217
+ focus:border-login/60 focus:ring-1 focus:ring-login/30
218
218
  `}
219
219
  spellCheck={false}
220
220
  />
221
221
  <div
222
- className='w-8 h-8 rounded border border-login-500 shrink-0'
222
+ className='w-8 h-8 rounded border border-login-500/50 shrink-0'
223
223
  style={{ backgroundColor: hexInput }}
224
224
  />
225
225
  </div>
226
226
 
227
- <div className='grid grid-cols-8 gap-1.5 pt-3 border-t border-login-500'>
227
+ <div className='grid grid-cols-8 gap-1.5 pt-3 border-t border-login-500/25'>
228
228
  {PRESET_COLORS.map(color => (
229
229
  <button
230
230
  key={color}