uibee 2.8.2 → 2.8.5

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.
@@ -0,0 +1,8 @@
1
+ import { ReactNode } from 'react';
2
+ type AlertProps = {
3
+ children: ReactNode;
4
+ variant?: 'warning' | 'info';
5
+ className?: string;
6
+ };
7
+ export default function Alert({ children, variant, className, }: AlertProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CircleAlert, TriangleAlert, Info } from 'lucide-react';
3
+ export default function Alert({ children, variant = 'warning', className = '', }) {
4
+ const color = variant === 'warning' ? 'bg-red-900'
5
+ : variant === 'info' ? 'bg-blue-600' :
6
+ 'bg-gray-900';
7
+ const Icon = variant === 'warning' ? CircleAlert
8
+ : variant === 'info' ? Info :
9
+ TriangleAlert;
10
+ return (_jsxs("div", { className: `
11
+ grid grid-cols-[min-content_auto] rounded-lg
12
+ p-[0.5em_1em_0.5em_0.8em] items-start w-fit text-white
13
+ ${color} ${className}
14
+ `, children: [_jsx(Icon, { className: 'w-8 h-8 mr-[0.3rem] stroke-login-50' }), _jsx("div", { className: 'self-center', children: children })] }));
15
+ }
@@ -19,3 +19,4 @@ export { default as VersionTag } from './version/version';
19
19
  export { default as LoginPage } from './login/loginPage';
20
20
  export { default as Toaster, toast } from './toast/toaster';
21
21
  export { default as Button } from './buttons/button';
22
+ export { default as Alert } from './alert/alert';
@@ -27,3 +27,5 @@ export { default as LoginPage } from './login/loginPage';
27
27
  export { default as Toaster, toast } from './toast/toaster';
28
28
  // Buttons
29
29
  export { default as Button } from './buttons/button';
30
+ // Alert
31
+ export { default as Alert } from './alert/alert';
@@ -16,5 +16,6 @@ export type SelectProps = {
16
16
  placeholder?: string;
17
17
  info?: string;
18
18
  clearable?: boolean;
19
+ searchable?: boolean;
19
20
  };
20
- export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder, info, clearable, }: SelectProps): import("react/jsx-runtime").JSX.Element;
21
+ export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder, info, clearable, searchable, }: SelectProps): import("react/jsx-runtime").JSX.Element;
@@ -3,11 +3,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect } from 'react';
4
4
  import Image from 'next/image';
5
5
  import { useClickOutside } from '../../hooks';
6
- import { ChevronDown, X } from 'lucide-react';
6
+ import { ChevronDown, X, Search } from 'lucide-react';
7
7
  import { FieldWrapper } from './shared';
8
- export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder = 'Select an option', info, clearable = true, }) {
8
+ export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder = 'Select an option', info, clearable = true, searchable = true, }) {
9
9
  const [isOpen, setIsOpen] = useState(false);
10
+ const [searchTerm, setSearchTerm] = useState('');
10
11
  const [selectedOption, setSelectedOption] = useState(options.find(opt => opt.value === value));
12
+ useEffect(() => {
13
+ if (!isOpen) {
14
+ setSearchTerm('');
15
+ }
16
+ }, [isOpen]);
11
17
  useEffect(() => {
12
18
  setSelectedOption(options.find(opt => opt.value === value));
13
19
  }, [value, options]);
@@ -30,6 +36,7 @@ export default function Select({ label, name, value, onChange, options, error, c
30
36
  onChange(null);
31
37
  }
32
38
  }
39
+ const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()));
33
40
  return (_jsxs(FieldWrapper, { label: label, name: name, required: required, info: info, error: error, className: className, children: [_jsxs("div", { className: 'relative', ref: containerRef, children: [_jsxs("button", { type: 'button', onClick: () => !disabled && setIsOpen(!isOpen), disabled: disabled, "aria-haspopup": 'listbox', "aria-expanded": isOpen, "aria-labelledby": label ? undefined : name, className: `
34
41
  w-full rounded-md bg-login-500/50 border border-login-500
35
42
  text-login-text text-left
@@ -47,13 +54,17 @@ export default function Select({ label, name, value, onChange, options, error, c
47
54
  text-login-200 pointer-events-none
48
55
  transition-transform duration-200
49
56
  ${isOpen ? 'rotate-180' : ''}
50
- `, children: _jsx(ChevronDown, { className: 'w-4 h-4' }) })] })] }), isOpen && (_jsx("div", { className: `
57
+ `, children: _jsx(ChevronDown, { className: 'w-4 h-4' }) })] })] }), isOpen && (_jsxs("div", { className: `
51
58
  absolute z-50 w-full mt-1 bg-login-600 border border-login-500
52
- rounded-md shadow-lg max-h-60 overflow-auto noscroll
53
- `, children: options.length > 0 ? (_jsx("ul", { className: 'py-1', role: 'listbox', children: options.map((option) => (_jsx("li", { role: 'option', "aria-selected": selectedOption?.value === option.value, children: _jsxs("button", { type: 'button', onClick: () => handleSelect(option), className: `
54
- w-full text-left px-3 py-2 text-sm
55
- hover:bg-login-500 transition-colors duration-150
56
- flex items-center gap-2
57
- ${selectedOption?.value === option.value ? 'bg-login-500 text-login' : 'text-login-text'}
58
- `, children: [option.image && (_jsx(Image, { src: option.image, alt: '', width: 75, height: 25, className: 'rounded-md object-cover shrink-0' })), _jsx("span", { className: 'truncate', children: option.label })] }) }, option.value))) })) : (_jsx("div", { className: 'px-3 py-2 text-sm text-login-200', children: "No options available" })) }))] }), _jsx("input", { type: 'hidden', name: name, value: selectedOption?.value || '', required: required })] }));
59
+ rounded-md shadow-lg max-h-60 overflow-hidden flex flex-col
60
+ `, children: [searchable && (_jsx("div", { className: 'p-2 sticky top-0 bg-login-600 border-b border-login-500 z-10', children: _jsxs("div", { className: 'relative', children: [_jsx(Search, { className: 'absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-login-200' }), _jsx("input", { type: 'text', value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), placeholder: 'Search...', autoFocus: true, className: `
61
+ w-full bg-login-500/50 border border-login-500 rounded-md
62
+ py-1.5 pl-9 pr-3 text-sm text-login-text
63
+ focus:outline-none focus:border-login focus:ring-1 focus:ring-login
64
+ ` })] }) })), _jsx("div", { className: 'overflow-auto noscroll', children: filteredOptions.length > 0 ? (_jsx("ul", { className: 'py-1', role: 'listbox', children: filteredOptions.map((option) => (_jsx("li", { role: 'option', "aria-selected": selectedOption?.value === option.value, children: _jsxs("button", { type: 'button', onClick: () => handleSelect(option), className: `
65
+ w-full text-left px-3 py-2 text-sm
66
+ hover:bg-login-500 transition-colors duration-150
67
+ flex items-center gap-2
68
+ ${selectedOption?.value === option.value ? 'bg-login-500 text-login' : 'text-login-text'}
69
+ `, children: [option.image && (_jsx(Image, { src: option.image, alt: '', width: 75, height: 25, className: 'rounded-md object-cover shrink-0' })), _jsx("span", { className: 'truncate', children: option.label })] }) }, option.value))) })) : (_jsx("div", { className: 'px-3 py-2 text-sm text-login-200', children: searchTerm ? 'No results found' : 'No options available' })) })] }))] }), _jsx("input", { type: 'hidden', name: name, value: selectedOption?.value || '', required: required })] }));
59
70
  }
@@ -1,2 +1,2 @@
1
1
  import { LoginPageProps } from 'uibee/components';
2
- export default function LoginPage({ title, description, redirectURL, version, btg, handleSubmit }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
2
+ export default function LoginPage({ title, description, redirectURL, version, btg, handleSubmit, guestRedirectURL, guestText }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { LogIn } from 'lucide-react';
3
3
  import Logo from '../logo/logo';
4
4
  import Link from 'next/link';
5
- export default function LoginPage({ title, description, redirectURL, version, btg, handleSubmit }) {
5
+ export default function LoginPage({ title, description, redirectURL, version, btg, handleSubmit, guestRedirectURL, guestText }) {
6
6
  return (_jsx("main", { className: 'w-full h-full flex items-center justify-center bg-login-800 p-8', children: _jsxs("div", { className: 'flex flex-col justify-center items-center bg-login-600 px-4 py-12 rounded-xl w-full max-w-md gap-4 md:gap-6', children: [_jsx("div", { className: 'relative aspect-3/1 w-full', children: _jsx(Logo, { className: 'object-contain px-6 sm:px-12' }) }), _jsxs("h1", { className: 'text-3xl font-extrabold text-login text-center tracking-tight', children: [title, " ", btg ? ' - Break the Glass' : ''] }), description && (_jsx("p", { className: 'text-center font-medium text-lg mb-2 max-w-xs', children: description })), btg ? (_jsxs("form", { className: 'w-full flex flex-col gap-3 max-w-xs', onSubmit: e => {
7
7
  e.preventDefault();
8
8
  handleSubmit?.(new FormData(e.currentTarget));
@@ -13,5 +13,6 @@ export default function LoginPage({ title, description, redirectURL, version, bt
13
13
  max-w-xs py-3 px-6 rounded-xl bg-login font-bold
14
14
  text-lg hover:bg-login/80 transition-all
15
15
  duration-200 mb-2 mt-2 cursor-pointer
16
- `, children: ["Login", _jsx(LogIn, { className: 'w-6 h-6' })] })), _jsxs("span", { className: 'text-sm mt-2', children: ["v", version] })] }) }));
16
+ `, children: ["Login", _jsx(LogIn, { className: 'w-6 h-6' })] })), guestRedirectURL &&
17
+ _jsx(Link, { href: guestRedirectURL, className: 'text-sm font-semibold cursor-pointer opacity-50', children: guestText || 'Continue as guest' }), _jsxs("span", { className: 'text-sm mt-2', children: ["v", version] })] }) }));
17
18
  }
@@ -10,12 +10,15 @@
10
10
  --color-red-200: oklch(88.5% 0.062 18.334);
11
11
  --color-red-400: oklch(70.4% 0.191 22.216);
12
12
  --color-red-500: oklch(63.7% 0.237 25.331);
13
+ --color-red-900: oklch(39.6% 0.141 25.723);
13
14
  --color-yellow-500: oklch(79.5% 0.184 86.047);
14
15
  --color-green-500: oklch(72.3% 0.219 149.579);
15
16
  --color-blue-500: oklch(62.3% 0.214 259.815);
17
+ --color-blue-600: oklch(54.6% 0.245 262.881);
16
18
  --color-gray-200: oklch(92.8% 0.006 264.531);
17
19
  --color-gray-300: oklch(87.2% 0.01 258.338);
18
20
  --color-gray-400: oklch(70.7% 0.022 261.325);
21
+ --color-gray-900: oklch(21% 0.034 264.665);
19
22
  --color-black: #000;
20
23
  --color-white: #fff;
21
24
  --spacing: 0.25rem;
@@ -293,6 +296,9 @@
293
296
  .relative {
294
297
  position: relative;
295
298
  }
299
+ .sticky {
300
+ position: sticky;
301
+ }
296
302
  .inset-0 {
297
303
  inset: calc(var(--spacing) * 0);
298
304
  }
@@ -347,6 +353,9 @@
347
353
  .left-2 {
348
354
  left: calc(var(--spacing) * 2);
349
355
  }
356
+ .left-2\.5 {
357
+ left: calc(var(--spacing) * 2.5);
358
+ }
350
359
  .left-3 {
351
360
  left: calc(var(--spacing) * 3);
352
361
  }
@@ -1004,6 +1013,9 @@
1004
1013
  .mt-2 {
1005
1014
  margin-top: calc(var(--spacing) * 2);
1006
1015
  }
1016
+ .mr-\[0\.3rem\] {
1017
+ margin-right: 0.3rem;
1018
+ }
1007
1019
  .mb-1 {
1008
1020
  margin-bottom: calc(var(--spacing) * 1);
1009
1021
  }
@@ -1292,6 +1304,9 @@
1292
1304
  .grid-cols-8 {
1293
1305
  grid-template-columns: repeat(8, minmax(0, 1fr));
1294
1306
  }
1307
+ .grid-cols-\[min-content_auto\] {
1308
+ grid-template-columns: min-content auto;
1309
+ }
1295
1310
  .flex-col {
1296
1311
  flex-direction: column;
1297
1312
  }
@@ -1310,6 +1325,9 @@
1310
1325
  .items-end {
1311
1326
  align-items: flex-end;
1312
1327
  }
1328
+ .items-start {
1329
+ align-items: flex-start;
1330
+ }
1313
1331
  .justify-between {
1314
1332
  justify-content: space-between;
1315
1333
  }
@@ -1344,6 +1362,9 @@
1344
1362
  margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
1345
1363
  }
1346
1364
  }
1365
+ .self-center {
1366
+ align-self: center;
1367
+ }
1347
1368
  .truncate {
1348
1369
  overflow: hidden;
1349
1370
  text-overflow: ellipsis;
@@ -1401,6 +1422,10 @@
1401
1422
  border-top-style: var(--tw-border-style);
1402
1423
  border-top-width: 1px;
1403
1424
  }
1425
+ .border-b {
1426
+ border-bottom-style: var(--tw-border-style);
1427
+ border-bottom-width: 1px;
1428
+ }
1404
1429
  .border-none {
1405
1430
  --tw-border-style: none;
1406
1431
  border-style: none;
@@ -1429,6 +1454,12 @@
1429
1454
  .bg-\[\#18181899\] {
1430
1455
  background-color: #18181899;
1431
1456
  }
1457
+ .bg-blue-600 {
1458
+ background-color: var(--color-blue-600);
1459
+ }
1460
+ .bg-gray-900 {
1461
+ background-color: var(--color-gray-900);
1462
+ }
1432
1463
  .bg-login {
1433
1464
  background-color: var(--color-login);
1434
1465
  }
@@ -1483,6 +1514,9 @@
1483
1514
  background-color: color-mix(in oklab, var(--color-login) 70%, transparent);
1484
1515
  }
1485
1516
  }
1517
+ .bg-red-900 {
1518
+ background-color: var(--color-red-900);
1519
+ }
1486
1520
  .bg-transparent {
1487
1521
  background-color: transparent;
1488
1522
  }
@@ -1539,6 +1573,9 @@
1539
1573
  .stroke-login {
1540
1574
  stroke: var(--color-login);
1541
1575
  }
1576
+ .stroke-login-50 {
1577
+ stroke: var(--color-login-50);
1578
+ }
1542
1579
  .stroke-\[3\.5px\] {
1543
1580
  stroke-width: 3.5px;
1544
1581
  }
@@ -1563,6 +1600,9 @@
1563
1600
  .p-8 {
1564
1601
  padding: calc(var(--spacing) * 8);
1565
1602
  }
1603
+ .p-\[0\.5em_1em_0\.5em_0\.8em\] {
1604
+ padding: 0.5em 1em 0.5em 0.8em;
1605
+ }
1566
1606
  .px-2 {
1567
1607
  padding-inline: calc(var(--spacing) * 2);
1568
1608
  }
@@ -1578,6 +1618,9 @@
1578
1618
  .py-1 {
1579
1619
  padding-block: calc(var(--spacing) * 1);
1580
1620
  }
1621
+ .py-1\.5 {
1622
+ padding-block: calc(var(--spacing) * 1.5);
1623
+ }
1581
1624
  .py-2 {
1582
1625
  padding-block: calc(var(--spacing) * 2);
1583
1626
  }
@@ -1617,6 +1660,9 @@
1617
1660
  .pl-4 {
1618
1661
  padding-left: calc(var(--spacing) * 4);
1619
1662
  }
1663
+ .pl-9 {
1664
+ padding-left: calc(var(--spacing) * 9);
1665
+ }
1620
1666
  .pl-10 {
1621
1667
  padding-left: calc(var(--spacing) * 10);
1622
1668
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.8.2",
3
+ "version": "2.8.5",
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": {
@@ -0,0 +1,35 @@
1
+ import { ReactNode } from 'react'
2
+ import { CircleAlert, TriangleAlert, Info } from 'lucide-react'
3
+
4
+ type AlertProps = {
5
+ children: ReactNode
6
+ variant?: 'warning' | 'info'
7
+ className?: string
8
+ }
9
+
10
+ export default function Alert({
11
+ children,
12
+ variant = 'warning',
13
+ className = '',
14
+ }: 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
22
+
23
+ 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>
34
+ )
35
+ }
@@ -34,3 +34,6 @@ export { default as Toaster, toast } from './toast/toaster'
34
34
 
35
35
  // Buttons
36
36
  export { default as Button } from './buttons/button'
37
+
38
+ // Alert
39
+ export { default as Alert } from './alert/alert'
@@ -3,7 +3,7 @@
3
3
  import { useState, useEffect } from 'react'
4
4
  import Image from 'next/image'
5
5
  import { useClickOutside } from '../../hooks'
6
- import { ChevronDown, X } from 'lucide-react'
6
+ import { ChevronDown, X, Search } from 'lucide-react'
7
7
  import { FieldWrapper } from './shared'
8
8
 
9
9
  export type Option = {
@@ -25,6 +25,7 @@ export type SelectProps = {
25
25
  placeholder?: string
26
26
  info?: string
27
27
  clearable?: boolean
28
+ searchable?: boolean
28
29
  }
29
30
 
30
31
  export default function Select({
@@ -40,12 +41,20 @@ export default function Select({
40
41
  placeholder = 'Select an option',
41
42
  info,
42
43
  clearable = true,
44
+ searchable = true,
43
45
  }: SelectProps) {
44
46
  const [isOpen, setIsOpen] = useState(false)
47
+ const [searchTerm, setSearchTerm] = useState('')
45
48
  const [selectedOption, setSelectedOption] = useState<Option | undefined>(
46
49
  options.find(opt => opt.value === value)
47
50
  )
48
51
 
52
+ useEffect(() => {
53
+ if (!isOpen) {
54
+ setSearchTerm('')
55
+ }
56
+ }, [isOpen])
57
+
49
58
  useEffect(() => {
50
59
  setSelectedOption(options.find(opt => opt.value === value))
51
60
  }, [value, options])
@@ -70,6 +79,10 @@ export default function Select({
70
79
  }
71
80
  }
72
81
 
82
+ const filteredOptions = options.filter(option =>
83
+ option.label.toLowerCase().includes(searchTerm.toLowerCase())
84
+ )
85
+
73
86
  return (
74
87
  <FieldWrapper
75
88
  label={label}
@@ -141,41 +154,62 @@ export default function Select({
141
154
  {isOpen && (
142
155
  <div className={`
143
156
  absolute z-50 w-full mt-1 bg-login-600 border border-login-500
144
- rounded-md shadow-lg max-h-60 overflow-auto noscroll
157
+ rounded-md shadow-lg max-h-60 overflow-hidden flex flex-col
145
158
  `}>
146
- {options.length > 0 ? (
147
- <ul className='py-1' role='listbox'>
148
- {options.map((option) => (
149
- <li key={option.value} role='option' aria-selected={selectedOption?.value === option.value}>
150
- <button
151
- type='button'
152
- onClick={() => handleSelect(option)}
153
- className={`
154
- w-full text-left px-3 py-2 text-sm
155
- hover:bg-login-500 transition-colors duration-150
156
- flex items-center gap-2
157
- ${selectedOption?.value === option.value ? 'bg-login-500 text-login' : 'text-login-text'}
158
- `}
159
- >
160
- {option.image && (
161
- <Image
162
- src={option.image}
163
- alt=''
164
- width={75}
165
- height={25}
166
- className='rounded-md object-cover shrink-0'
167
- />
168
- )}
169
- <span className='truncate'>{option.label}</span>
170
- </button>
171
- </li>
172
- ))}
173
- </ul>
174
- ) : (
175
- <div className='px-3 py-2 text-sm text-login-200'>
176
- No options available
159
+ {searchable && (
160
+ <div className='p-2 sticky top-0 bg-login-600 border-b border-login-500 z-10'>
161
+ <div className='relative'>
162
+ <Search className='absolute left-2.5 top-1/2 -translate-y-1/2 w-4 h-4 text-login-200' />
163
+ <input
164
+ type='text'
165
+ value={searchTerm}
166
+ onChange={(e) => setSearchTerm(e.target.value)}
167
+ placeholder='Search...'
168
+ autoFocus
169
+ className={`
170
+ w-full bg-login-500/50 border border-login-500 rounded-md
171
+ py-1.5 pl-9 pr-3 text-sm text-login-text
172
+ focus:outline-none focus:border-login focus:ring-1 focus:ring-login
173
+ `}
174
+ />
175
+ </div>
177
176
  </div>
178
177
  )}
178
+ <div className='overflow-auto noscroll'>
179
+ {filteredOptions.length > 0 ? (
180
+ <ul className='py-1' role='listbox'>
181
+ {filteredOptions.map((option) => (
182
+ <li key={option.value} role='option' aria-selected={selectedOption?.value === option.value}>
183
+ <button
184
+ type='button'
185
+ onClick={() => handleSelect(option)}
186
+ className={`
187
+ w-full text-left px-3 py-2 text-sm
188
+ hover:bg-login-500 transition-colors duration-150
189
+ flex items-center gap-2
190
+ ${selectedOption?.value === option.value ? 'bg-login-500 text-login': 'text-login-text'}
191
+ `}
192
+ >
193
+ {option.image && (
194
+ <Image
195
+ src={option.image}
196
+ alt=''
197
+ width={75}
198
+ height={25}
199
+ className='rounded-md object-cover shrink-0'
200
+ />
201
+ )}
202
+ <span className='truncate'>{option.label}</span>
203
+ </button>
204
+ </li>
205
+ ))}
206
+ </ul>
207
+ ) : (
208
+ <div className='px-3 py-2 text-sm text-login-200'>
209
+ {searchTerm ? 'No results found' : 'No options available'}
210
+ </div>
211
+ )}
212
+ </div>
179
213
  </div>
180
214
  )}
181
215
  </div>
@@ -3,7 +3,16 @@ import { LogIn } from 'lucide-react'
3
3
  import Logo from '@components/logo/logo'
4
4
  import Link from 'next/link'
5
5
 
6
- export default function LoginPage({ title, description, redirectURL, version, btg, handleSubmit }: LoginPageProps) {
6
+ export default function LoginPage({
7
+ title,
8
+ description,
9
+ redirectURL,
10
+ version,
11
+ btg,
12
+ handleSubmit,
13
+ guestRedirectURL,
14
+ guestText
15
+ }: LoginPageProps) {
7
16
  return (
8
17
  <main className='w-full h-full flex items-center justify-center bg-login-800 p-8'>
9
18
  <div
@@ -69,6 +78,14 @@ export default function LoginPage({ title, description, redirectURL, version, bt
69
78
  <LogIn className='w-6 h-6' />
70
79
  </Link>
71
80
  )}
81
+ {guestRedirectURL &&
82
+ <Link
83
+ href={guestRedirectURL}
84
+ className='text-sm font-semibold cursor-pointer opacity-50'
85
+ >
86
+ {guestText || 'Continue as guest'}
87
+ </Link>
88
+ }
72
89
  <span className='text-sm mt-2'>v{version}</span>
73
90
  </div>
74
91
  </main>
@@ -7,6 +7,8 @@ declare module 'uibee/components' {
7
7
  version: string
8
8
  btg?: boolean
9
9
  handleSubmit?: (formData: FormData) => void
10
+ guestRedirectURL?: string
11
+ guestText?: string
10
12
  }
11
13
 
12
14
  export type ToastType = 'info' | 'success' | 'warning' | 'error'