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,13 @@
1
+ import { useEffect } from "react";
2
+ //#region src/hooks/useClickOutside.ts
3
+ function useClickOutside(ref, callback) {
4
+ useEffect(() => {
5
+ function handleClickOutside(event) {
6
+ if (ref.current && !ref.current.contains(event.target)) callback();
7
+ }
8
+ document.addEventListener("mousedown", handleClickOutside);
9
+ return () => document.removeEventListener("mousedown", handleClickOutside);
10
+ }, [ref, callback]);
11
+ }
12
+ //#endregion
13
+ export { useClickOutside as t };
package/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export * from './components'
2
2
  export * from './hooks'
3
3
  export * from './utils'
4
- export * from './scripts'
5
4
  export * from './styles'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.17.4",
3
+ "version": "3.0.0",
4
4
  "description": "Shared components, functions and hooks for reuse across Login projects",
5
5
  "homepage": "https://github.com/Login-Linjeforening-for-IT/uibee#readme",
6
6
  "bugs": {
@@ -18,43 +18,42 @@
18
18
  "exports": {
19
19
  "./hooks": "./dist/src/hooks/index.js",
20
20
  "./components": "./dist/src/components/index.js",
21
- "./scripts": "./dist/src/scripts/index.js",
22
21
  "./utils": "./dist/src/utils/index.js",
23
- "./styles": "./dist/src/styles/index.js"
22
+ "./styles": "./dist/src/styles/index.js",
23
+ "./icons": "./dist/src/icons/index.js"
24
24
  },
25
25
  "scripts": {
26
- "build:clean": "rm -rf dist",
27
- "build:css": "node src/scripts/buildCss.ts",
28
- "build:rewrite": "node dist/src/scripts/rewriteAlias.js",
29
- "build:ts": "tsc --declaration",
30
- "build": "npm run build:clean && npm run build:css && npm run build:ts && npm run build:rewrite",
26
+ "build": "tsc --noEmit && tsdown",
27
+ "dev": "tsdown --watch",
31
28
  "lint": "eslint ./src",
32
29
  "lint:fix": "eslint --fix ./src"
33
30
  },
34
31
  "peerDependencies": {
35
- "next": "^16.2.1",
36
- "react": "^19.2.4"
32
+ "next": "^16.2.9",
33
+ "react": "^19.2.7"
37
34
  },
38
35
  "devDependencies": {
39
36
  "@stylistic/eslint-plugin": "^5.10.0",
40
- "@tailwindcss/postcss": "^4.2.2",
41
- "@tailwindcss/typography": "^0.5.19",
42
- "@types/node": "^25.6.0",
43
- "@types/react": "19.2.14",
37
+ "@tailwindcss/postcss": "^4.3.1",
38
+ "@tailwindcss/typography": "^0.5.20",
39
+ "@tsdown/css": "^0.22.3",
40
+ "@types/bun": "^1.3.14",
41
+ "@types/react": "^19.2.17",
44
42
  "@types/react-dom": "^19.2.3",
45
- "eslint": "^10.2.1",
46
- "glob": "^13.0.6",
47
- "postcss": "^8.5.10",
48
- "tailwindcss": "^4.2.2",
43
+ "eslint": "^10.5.0",
44
+ "postcss": "^8.5.15",
45
+ "postcss-import": "^16.1.1",
46
+ "tailwindcss": "^4.3.1",
47
+ "tsdown": "^0.22.3",
49
48
  "typescript": "^6.0.3",
50
- "typescript-eslint": "^8.58.2"
49
+ "typescript-eslint": "^8.61.1"
51
50
  },
52
51
  "dependencies": {
53
- "lucide-react": "^1.8.0",
54
- "react-dom": "19.2.5",
52
+ "lucide-react": "^1.21.0",
53
+ "react-dom": "^19.2.7",
55
54
  "react-markdown": "^10.1.0",
56
55
  "rehype-highlight": "^7.0.2",
57
56
  "remark-gfm": "^4.0.1",
58
- "utilbee": "^1.4.5"
57
+ "utilbee": "^1.4.7"
59
58
  }
60
- }
59
+ }
@@ -1,5 +1,6 @@
1
1
  import { ReactNode } from 'react'
2
- import { CircleAlert, TriangleAlert, Info } from 'lucide-react'
2
+ import { CircleAlert, Info } from 'lucide-react'
3
+ import LeftBarPanel from '@components/container/leftBarPanel'
3
4
 
4
5
  type AlertProps = {
5
6
  children: ReactNode
@@ -7,29 +8,30 @@ type AlertProps = {
7
8
  className?: string
8
9
  }
9
10
 
11
+ const styles = {
12
+ warning: {
13
+ color: 'border-l-red-500',
14
+ iconClass: 'w-4 h-4 shrink-0 stroke-red-400 mr-3 mt-0.5',
15
+ Icon: CircleAlert,
16
+ },
17
+ info: {
18
+ color: 'border-l-blue-500',
19
+ iconClass: 'w-4 h-4 shrink-0 stroke-blue-400 mr-3 mt-0.5',
20
+ Icon: Info,
21
+ },
22
+ }
23
+
10
24
  export default function Alert({
11
25
  children,
12
26
  variant = 'warning',
13
27
  className = '',
14
28
  }: AlertProps) {
15
- const color = variant === 'warning' ? 'bg-red-900'
16
- : variant === 'info' ? 'bg-blue-600' :
17
- 'bg-gray-900'
18
-
19
- const Icon = variant === 'warning' ? CircleAlert
20
- : variant === 'info' ? Info :
21
- TriangleAlert
29
+ const { color, iconClass, Icon } = styles[variant] ?? styles.warning
22
30
 
23
31
  return (
24
- <div
25
- className={`
26
- grid grid-cols-[min-content_auto] rounded-lg
27
- p-[0.5em_1em_0.5em_0.8em] items-start w-fit text-white
28
- ${color} ${className}
29
- `}
30
- >
31
- <Icon className='w-8 h-8 mr-[0.3rem] stroke-login-50' />
32
- <div className='self-center'>{children}</div>
33
- </div>
32
+ <LeftBarPanel color={color} className={`flex items-start px-4 py-3 text-sm text-login-100 ${className}`}>
33
+ <Icon className={iconClass} />
34
+ <div className='leading-relaxed'>{children}</div>
35
+ </LeftBarPanel>
34
36
  )
35
37
  }
@@ -2,25 +2,31 @@ import Link from 'next/link'
2
2
  import type { JSX } from 'react'
3
3
 
4
4
  type ButtonProps = {
5
- text: string
5
+ text?: string
6
6
  className?: string
7
- icon: string | JSX.Element
7
+ icon?: string | JSX.Element
8
8
  path?: string
9
9
  type?: 'button' | 'submit' | 'reset'
10
10
  variant?: 'primary' | 'secondary' | 'warning' | 'danger' | 'success' | 'info'
11
- onClick?: (_: object | string) => void
11
+ onClick?: (e: React.MouseEvent) => void
12
12
  disabled?: boolean
13
13
  }
14
14
 
15
15
  const variants = {
16
- primary: 'bg-login/70 outline-login hover:bg-login/90',
17
- secondary: 'bg-login-500/70 outline-login-500 hover:bg-login-500/90',
18
- warning: 'bg-yellow-500/70 outline-yellow-500 hover:bg-yellow-500/90',
19
- danger: 'bg-red-600/70 outline-red-600 hover:bg-red-600/90',
20
- success: 'bg-green-600/70 outline-green-600 hover:bg-green-600/90',
21
- info: 'bg-blue-600/70 outline-blue-600 hover:bg-blue-600/90'
16
+ primary: 'bg-login text-white hover:brightness-110',
17
+ secondary: 'bg-login-500 text-login-50 hover:bg-login-400',
18
+ warning: 'bg-yellow-500 text-black hover:brightness-110',
19
+ danger: 'bg-red-600 text-white hover:brightness-110',
20
+ success: 'bg-green-600 text-white hover:brightness-110',
21
+ info: 'bg-blue-600 text-white hover:brightness-110',
22
22
  }
23
23
 
24
+ const base = 'cursor-pointer px-4 rounded-md h-9 flex items-center justify-center gap-2 select-none text-sm font-medium transition-all duration-150 w-fit'
25
+ const disabledCls = 'opacity-40 cursor-not-allowed pointer-events-none'
26
+
27
+ const iconWrap = (icon: ButtonProps['icon']) =>
28
+ icon ? <span className='[&>svg]:w-4 [&>svg]:h-4 flex items-center'>{icon}</span> : null
29
+
24
30
  export default function Button({
25
31
  text,
26
32
  className,
@@ -29,9 +35,10 @@ export default function Button({
29
35
  variant = 'primary',
30
36
  type,
31
37
  onClick,
32
- disabled
38
+ disabled,
33
39
  }: ButtonProps) {
34
- const bg = variants[variant]
40
+ const color = variants[variant]
41
+ const cls = `${base} ${color} ${disabled ? disabledCls : ''} ${className || ''}`
35
42
 
36
43
  if (!path) {
37
44
  return (
@@ -40,42 +47,28 @@ export default function Button({
40
47
  disabled={disabled}
41
48
  onClick={onClick}
42
49
  aria-label={text}
43
- className={`
44
- ${bg} cursor-pointer px-4 rounded-md min-h-8 h-8 flex
45
- justify-evenly items-center gap-2 select-none
46
- focus:outline-none border-0 outline w-fit ${className}
47
- `}
50
+ className={cls}
48
51
  >
49
- <h1 className='font-bold'>{icon || ''}</h1>
50
- <h1 className='min-w-fit w-fit'>{text}</h1>
52
+ {iconWrap(icon)}
53
+ {text}
51
54
  </button>
52
55
  )
53
56
  }
54
57
 
55
58
  if (disabled) {
56
59
  return (
57
- <div
58
- className={`
59
- ${bg} cursor-not-allowed px-4 rounded-md h-8 flex w-fit
60
- justify-evenly items-center gap-2 select-none ${className}
61
- `}
62
- >
63
- <h1 className='font-bold'>{icon || ''}</h1>
64
- <h1 className='min-w-fit w-fit'>{text}</h1>
60
+ <div className={cls}>
61
+ {iconWrap(icon)}
62
+ {text}
65
63
  </div>
66
64
  )
67
65
  }
68
66
 
69
67
  return (
70
- <Link
71
- href={path}
72
- className={`
73
- ${bg} cursor-pointer px-4 rounded-md h-8 flex w-fit
74
- justify-evenly items-center gap-2 select-none ${className}
75
- `}
76
- >
77
- <h1 className='font-bold'>{icon || ''}</h1>
78
- <h1 className='min-w-fit w-fit'>{text}</h1>
68
+ <Link href={path} className={`${base} ${color} ${className || ''}`}>
69
+ {iconWrap(icon)}
70
+ {text}
79
71
  </Link>
80
72
  )
81
73
  }
74
+
@@ -25,12 +25,12 @@ export default function ConfirmPopup({
25
25
  }: ConfirmPopupProps) {
26
26
  if (!isOpen) return null
27
27
 
28
- const confirmBg =
28
+ const confirmClass =
29
29
  variant === 'danger'
30
- ? 'bg-red-700/80 outline-red-700 hover:bg-red-700'
30
+ ? 'bg-red-600 hover:brightness-110 text-white'
31
31
  : variant === 'warning'
32
- ? 'bg-yellow-600/80 outline-yellow-600 hover:bg-yellow-600'
33
- : 'bg-login/70 outline-login hover:bg-login/90'
32
+ ? 'bg-yellow-500 hover:brightness-110 text-black'
33
+ : 'bg-login hover:brightness-110 text-white'
34
34
 
35
35
  return (
36
36
  <div
@@ -40,7 +40,7 @@ export default function ConfirmPopup({
40
40
  className='fixed inset-0 z-50 flex items-center justify-center'
41
41
  onClick={onCancel}
42
42
  >
43
- <div className='absolute inset-0 bg-black/60 backdrop-blur-sm' />
43
+ <div className='absolute inset-0 bg-black/50 backdrop-blur-sm' />
44
44
 
45
45
  <div
46
46
  className='
@@ -50,35 +50,34 @@ export default function ConfirmPopup({
50
50
  '
51
51
  onClick={(e) => e.stopPropagation()}
52
52
  >
53
- <div className='flex items-center gap-3'>
53
+ <div className='flex items-start gap-3'>
54
54
  {variant !== 'default' && (
55
55
  <TriangleAlert
56
- className={`w-6 h-6 shrink-0 ${variant === 'danger' ? 'stroke-red-400' : 'stroke-yellow-400'}`}
56
+ className={`w-5 h-5 shrink-0 mt-0.5 ${variant === 'danger' ? 'stroke-red-400' : 'stroke-yellow-400'}`}
57
57
  />
58
58
  )}
59
59
  <h2
60
60
  id='confirm-popup-header'
61
- className='text-login-50 text-lg font-bold leading-snug'
61
+ className='text-login-50 text-base font-semibold leading-snug'
62
62
  >
63
63
  {header}
64
64
  </h2>
65
65
  </div>
66
66
 
67
67
  {description && (
68
- <p className='text-login-100 text-sm leading-relaxed'>
68
+ <p className='text-login-200 text-sm leading-relaxed'>
69
69
  {description}
70
70
  </p>
71
71
  )}
72
72
 
73
- <div className='flex justify-end gap-3 mt-1'>
73
+ <div className='flex justify-end gap-2 mt-1'>
74
74
  <button
75
75
  type='button'
76
76
  onClick={onCancel}
77
77
  className='
78
78
  cursor-pointer px-4 py-1.5 rounded-md text-sm font-medium
79
- bg-login-500/60 text-login-50 outline outline-login-500/60
80
- hover:bg-login-500/90 focus:outline-none select-none
81
- transition-colors duration-150
79
+ bg-login-600 hover:bg-login-500 text-login-100
80
+ transition-colors duration-150 select-none
82
81
  '
83
82
  >
84
83
  {cancelText}
@@ -88,10 +87,9 @@ export default function ConfirmPopup({
88
87
  type='button'
89
88
  onClick={onConfirm}
90
89
  className={`
91
- cursor-pointer px-4 py-1.5 rounded-md text-sm font-bold
92
- text-white outline focus:outline-none select-none
93
- transition-colors duration-150
94
- ${confirmBg}
90
+ cursor-pointer px-4 py-1.5 rounded-md text-sm font-medium
91
+ transition-all duration-150 select-none
92
+ ${confirmClass}
95
93
  `}
96
94
  >
97
95
  {confirmText}
@@ -0,0 +1,54 @@
1
+ 'use client'
2
+
3
+ import { createContext, useContext, useState, type ReactNode } from 'react'
4
+ import { ChevronDown } from 'lucide-react'
5
+
6
+ const GroupContext = createContext(false)
7
+
8
+ type AccordionProps = {
9
+ title: ReactNode
10
+ children: ReactNode
11
+ defaultOpen?: boolean
12
+ className?: string
13
+ }
14
+
15
+ type AccordionGroupProps = {
16
+ children: ReactNode
17
+ className?: string
18
+ }
19
+
20
+ export function AccordionGroup({ children, className = '' }: AccordionGroupProps) {
21
+ return (
22
+ <GroupContext.Provider value={true}>
23
+ <div className={`rounded-lg border border-login-500/40 overflow-hidden divide-y divide-login-500/25 ${className}`}>
24
+ {children}
25
+ </div>
26
+ </GroupContext.Provider>
27
+ )
28
+ }
29
+
30
+ export default function Accordion({ title, children, defaultOpen = false, className = '' }: AccordionProps) {
31
+ const [open, setOpen] = useState(defaultOpen)
32
+ const grouped = useContext(GroupContext)
33
+
34
+ return (
35
+ <div className={grouped ? className : `rounded-lg border border-login-500/40 overflow-hidden ${className}`}>
36
+ <button
37
+ type='button'
38
+ onClick={() => setOpen((o) => !o)}
39
+ className='flex w-full cursor-pointer items-center justify-between bg-login-800 px-4 py-3 text-left transition-colors duration-150 hover:bg-login-700 select-none'
40
+ >
41
+ <div className='text-sm font-medium text-login-50'>{title}</div>
42
+ <ChevronDown
43
+ size={16}
44
+ className={`shrink-0 text-login-300 transition-transform duration-200 ${open ? 'rotate-180' : ''}`}
45
+ />
46
+ </button>
47
+ <div className={`overflow-hidden bg-login-900 transition-all duration-200 ${open ? 'max-h-[9999px]' : 'max-h-0'}`}>
48
+ <div className='px-4 py-3 text-sm text-login-100'>
49
+ {children}
50
+ </div>
51
+ </div>
52
+ </div>
53
+ )
54
+ }
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ type GlassCardProps = {
4
+ children: ReactNode
5
+ className?: string
6
+ }
7
+
8
+ export default function GlassCard({ children, className = '' }: GlassCardProps) {
9
+ return (
10
+ <section className={`rounded-xl border border-login-500/30 bg-login-800 ${className}`}>
11
+ {children}
12
+ </section>
13
+ )
14
+ }
@@ -0,0 +1,30 @@
1
+ import type { ElementType } from 'react'
2
+
3
+ const tones = {
4
+ amber: 'bg-amber-500/10 text-amber-400',
5
+ blue: 'bg-sky-500/10 text-sky-400',
6
+ emerald: 'bg-emerald-500/10 text-emerald-400',
7
+ rose: 'bg-rose-500/10 text-rose-400',
8
+ slate: 'bg-login-600 text-login-100',
9
+ violet: 'bg-violet-500/10 text-violet-400',
10
+ orange: 'bg-login/10 text-login',
11
+ } as const
12
+
13
+ type IconBubbleProps = {
14
+ icon: ElementType
15
+ tone?: keyof typeof tones
16
+ size?: 'sm' | 'md' | 'lg'
17
+ }
18
+
19
+ export type IconBubbleTone = keyof typeof tones
20
+
21
+ export default function IconBubble({ icon: Icon, tone = 'slate', size = 'md' }: IconBubbleProps) {
22
+ const sizeClass = size === 'sm' ? 'h-7 w-7' : size === 'lg' ? 'h-11 w-11' : 'h-9 w-9'
23
+ const iconSize = size === 'sm' ? 'h-3.5 w-3.5' : size === 'lg' ? 'h-5 w-5' : 'h-4 w-4'
24
+
25
+ return (
26
+ <div className={`flex shrink-0 items-center justify-center rounded-lg ${sizeClass} ${tones[tone]}`}>
27
+ <Icon className={iconSize} />
28
+ </div>
29
+ )
30
+ }
@@ -0,0 +1,15 @@
1
+ import { ReactNode } from 'react'
2
+
3
+ type LeftBarPanelProps = {
4
+ color: string
5
+ children: ReactNode
6
+ className?: string
7
+ }
8
+
9
+ export default function LeftBarPanel({ color, children, className = '' }: LeftBarPanelProps) {
10
+ return (
11
+ <div className={`bg-login-500/50 border border-login-500/30 border-l-2 rounded-sm ${color} ${className}`}>
12
+ {children}
13
+ </div>
14
+ )
15
+ }
@@ -0,0 +1,31 @@
1
+ type PulseDotVariant = 'online' | 'offline' | 'warning' | 'unknown'
2
+
3
+ type PulseDotProps = {
4
+ variant?: PulseDotVariant
5
+ size?: 'sm' | 'md' | 'lg'
6
+ }
7
+
8
+ const variantColor: Record<PulseDotVariant, string> = {
9
+ online: 'bg-green-500',
10
+ offline: 'bg-red-500',
11
+ warning: 'bg-login',
12
+ unknown: 'bg-login-400',
13
+ }
14
+
15
+ const sizeClass = {
16
+ sm: 'w-1.5 h-1.5',
17
+ md: 'w-2 h-2',
18
+ lg: 'w-3 h-3',
19
+ }
20
+
21
+ export default function PulseDot({ variant = 'online', size = 'md' }: PulseDotProps) {
22
+ const color = variantColor[variant]
23
+ const dotSize = sizeClass[size]
24
+
25
+ return (
26
+ <div className={`relative grid place-items-center ${dotSize}`}>
27
+ <span className={`absolute inline-flex h-full w-full rounded-full ${color} opacity-50 animate-ping`} />
28
+ <span className={`relative inline-flex rounded-full ${dotSize} ${color}`} />
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,24 @@
1
+ import type { ElementType } from 'react'
2
+ import GlassCard from './glassCard'
3
+ import IconBubble, { type IconBubbleTone } from './iconBubble'
4
+
5
+ type StatCardProps = {
6
+ label: string
7
+ value: string
8
+ icon: ElementType
9
+ tone?: IconBubbleTone
10
+ }
11
+
12
+ export default function StatCard({ label, value, icon, tone = 'slate' }: StatCardProps) {
13
+ return (
14
+ <GlassCard className='p-4'>
15
+ <div className='mb-3 flex items-center gap-3'>
16
+ <IconBubble icon={icon} tone={tone} />
17
+ <span className='text-sm font-medium text-login-200'>{label}</span>
18
+ </div>
19
+ <div className='truncate text-lg font-semibold text-login-50' title={value}>
20
+ {value}
21
+ </div>
22
+ </GlassCard>
23
+ )
24
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useState, type ReactNode } from 'react'
4
+
5
+ type Tab = {
6
+ id: string
7
+ label: ReactNode
8
+ }
9
+
10
+ type TabsProps = {
11
+ tabs: Tab[]
12
+ defaultTab?: string
13
+ activeTab?: string
14
+ onTabChange?: (id: string) => void
15
+ children: ReactNode
16
+ className?: string
17
+ }
18
+
19
+ type TabPanelProps = {
20
+ id: string
21
+ activeTab: string
22
+ children: ReactNode
23
+ }
24
+
25
+ export function Tabs({ tabs, defaultTab, activeTab: controlledTab, onTabChange, children, className = '' }: TabsProps) {
26
+ const [internalTab, setInternalTab] = useState(defaultTab ?? tabs[0]?.id ?? '')
27
+ const activeTab = controlledTab ?? internalTab
28
+
29
+ function handleTabChange(id: string) {
30
+ if (!controlledTab) setInternalTab(id)
31
+ onTabChange?.(id)
32
+ }
33
+
34
+ return (
35
+ <div className={className}>
36
+ <div className='flex gap-1 rounded-md bg-login-500/50 border border-login-500/40 p-1 w-fit'>
37
+ {tabs.map((tab) => (
38
+ <button
39
+ key={tab.id}
40
+ type='button'
41
+ onClick={() => handleTabChange(tab.id)}
42
+ className={`
43
+ px-3 py-1.5 rounded text-sm font-medium transition-all duration-150 cursor-pointer select-none
44
+ ${activeTab === tab.id
45
+ ? 'bg-login text-white shadow-sm'
46
+ : 'text-login-200 hover:text-login-50 hover:bg-login-600'
47
+ }
48
+ `}
49
+ >
50
+ {tab.label}
51
+ </button>
52
+ ))}
53
+ </div>
54
+ <div className='mt-3'>
55
+ {children}
56
+ </div>
57
+ </div>
58
+ )
59
+ }
60
+
61
+ export function TabPanel({ id, activeTab, children }: TabPanelProps) {
62
+ if (activeTab !== id) return null
63
+ return <div>{children}</div>
64
+ }