uibee 2.7.9 → 2.7.11

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.
@@ -2,6 +2,7 @@ export { default as Input } from './inputs/input';
2
2
  export { default as Textarea } from './inputs/textarea';
3
3
  export { default as Checkbox } from './inputs/checkbox';
4
4
  export { default as Select } from './inputs/select';
5
+ export { default as TagInput } from './inputs/tagInput';
5
6
  export { default as Switch } from './inputs/switch';
6
7
  export { default as Radio } from './inputs/radio';
7
8
  export { default as Range } from './inputs/range';
@@ -3,6 +3,7 @@ export { default as Input } from './inputs/input';
3
3
  export { default as Textarea } from './inputs/textarea';
4
4
  export { default as Checkbox } from './inputs/checkbox';
5
5
  export { default as Select } from './inputs/select';
6
+ export { default as TagInput } from './inputs/tagInput';
6
7
  export { default as Switch } from './inputs/switch';
7
8
  export { default as Radio } from './inputs/radio';
8
9
  export { default as Range } from './inputs/range';
@@ -69,7 +69,7 @@ export default function Input({ label, name, type = 'text', placeholder, value,
69
69
  text-login-text placeholder-login-200
70
70
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
71
71
  disabled:opacity-50 disabled:cursor-not-allowed
72
- py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
72
+ h-10.5 py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
73
73
  transition-all duration-200
74
74
  input-reset
75
75
  ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
@@ -35,7 +35,7 @@ export default function Select({ label, name, value, onChange, options, error, c
35
35
  text-login-text text-left
36
36
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
37
37
  disabled:opacity-50 disabled:cursor-not-allowed
38
- py-2 pl-3 pr-10
38
+ h-10.5 py-2 pl-3 pr-10
39
39
  transition-all duration-200
40
40
  flex items-center justify-between
41
41
  ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
@@ -1,14 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { SelectionWrapper } from './shared';
3
3
  export default function Switch({ label, name, checked, onChange, className, disabled, error, info, required, }) {
4
- return (_jsx(SelectionWrapper, { label: label, name: name, required: required, info: info, error: error, className: className, disabled: disabled, children: _jsxs("label", { className: 'relative inline-flex items-center cursor-pointer', children: [_jsx("input", { type: 'checkbox', id: name, name: name, checked: checked, onChange: onChange, disabled: disabled, required: required, className: 'sr-only peer' }), _jsx("div", { className: `
5
- w-11 h-6 bg-login-500/50 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-login/50
6
- rounded-full peer
4
+ return (_jsx(SelectionWrapper, { label: label, name: name, required: required, info: info, error: error, className: className, disabled: disabled, children: _jsxs("label", { className: 'relative inline-flex items-center cursor-pointer h-10.5', children: [_jsx("input", { type: 'checkbox', id: name, name: name, checked: checked, onChange: onChange, disabled: disabled, required: required, className: 'sr-only peer' }), _jsx("div", { className: `
5
+ w-11 h-6 bg-login-500/50 rounded-full peer
7
6
  peer-checked:after:translate-x-full peer-checked:after:border-white
8
- after:content-[''] after:absolute after:top-0.5 after:left-0.5
7
+ after:content-[''] after:absolute after:top-2.75 after:left-0.5
9
8
  after:bg-white after:border-gray-300 after:border after:rounded-full
10
- after:h-5 after:w-5 after:transition-all
11
- peer-checked:bg-login
9
+ after:h-5 after:w-5 after:transition-all peer-checked:bg-login
12
10
  ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
13
11
  ${error ? 'ring-1 ring-red-500' : ''}
14
12
  ` })] }) }));
@@ -0,0 +1,13 @@
1
+ export type TagInputProps = {
2
+ label?: string;
3
+ name: string;
4
+ value?: string[];
5
+ onChange?: (value: string[]) => void;
6
+ placeholder?: string;
7
+ error?: string;
8
+ className?: string;
9
+ disabled?: boolean;
10
+ required?: boolean;
11
+ info?: string;
12
+ };
13
+ export default function TagInput({ label, name, value, onChange, placeholder, error, className, disabled, required, info, }: TagInputProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,40 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { X } from 'lucide-react';
5
+ import { FieldWrapper } from './shared';
6
+ export default function TagInput({ label, name, value = [], onChange, placeholder = 'Add...', error, className, disabled, required, info, }) {
7
+ const [inputValue, setInputValue] = useState('');
8
+ function handleKeyDown(e) {
9
+ if ((e.key === 'Enter' || e.key === ',') && inputValue.trim()) {
10
+ e.preventDefault();
11
+ const val = inputValue.trim().replace(/,$/, '');
12
+ if (val && !value.includes(val)) {
13
+ const newValue = [...value, val];
14
+ if (onChange)
15
+ onChange(newValue);
16
+ }
17
+ setInputValue('');
18
+ }
19
+ else if (e.key === 'Backspace' && !inputValue && value.length > 0) {
20
+ const newValue = value.slice(0, -1);
21
+ if (onChange)
22
+ onChange(newValue);
23
+ }
24
+ }
25
+ function removeTag(index) {
26
+ if (disabled)
27
+ return;
28
+ const newValue = value.filter((_, i) => i !== index);
29
+ if (onChange)
30
+ onChange(newValue);
31
+ }
32
+ return (_jsxs(FieldWrapper, { label: label, name: name, required: required, info: info, error: error, className: className, children: [_jsxs("div", { className: `
33
+ flex flex-wrap gap-2 p-2 rounded-md bg-login-500/50 border border-login-500
34
+ text-login-text min-h-10.5
35
+ focus-within:border-login focus-within:ring-1 focus-within:ring-login
36
+ disabled:opacity-50 disabled:cursor-not-allowed
37
+ transition-all duration-200
38
+ ${error ? 'border-red-500 focus-within:border-red-500 focus-within:ring-red-500' : ''}
39
+ `, children: [value.map((tag, index) => (_jsxs("span", { className: 'flex items-center gap-1 px-2 py-1 bg-login rounded text-sm text-white', children: [tag, !disabled && (_jsx("button", { type: 'button', onClick: () => removeTag(index), className: 'hover:text-red-200 transition-colors', children: _jsx(X, { size: 14 }) }))] }, index))), _jsx("input", { type: 'text', value: inputValue, required: required && value.length === 0, onChange: (e) => setInputValue(e.target.value), onKeyDown: handleKeyDown, disabled: disabled, placeholder: value.length === 0 ? placeholder : '', className: 'flex-1 bg-transparent outline-none min-w-30 text-login-text placeholder:text-login-200' })] }), _jsx("input", { type: 'hidden', name: name, value: value.join(',') })] }));
40
+ }
@@ -7,6 +7,7 @@
7
7
  "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
8
8
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
9
9
  "Courier New", monospace;
10
+ --color-red-200: oklch(88.5% 0.062 18.334);
10
11
  --color-red-400: oklch(70.4% 0.191 22.216);
11
12
  --color-red-500: oklch(63.7% 0.237 25.331);
12
13
  --color-yellow-500: oklch(79.5% 0.184 86.047);
@@ -1071,6 +1072,9 @@
1071
1072
  .h-8 {
1072
1073
  height: calc(var(--spacing) * 8);
1073
1074
  }
1075
+ .h-10\.5 {
1076
+ height: calc(var(--spacing) * 10.5);
1077
+ }
1074
1078
  .h-11 {
1075
1079
  height: calc(var(--spacing) * 11);
1076
1080
  }
@@ -1098,6 +1102,9 @@
1098
1102
  .min-h-8 {
1099
1103
  min-height: calc(var(--spacing) * 8);
1100
1104
  }
1105
+ .min-h-10\.5 {
1106
+ min-height: calc(var(--spacing) * 10.5);
1107
+ }
1101
1108
  .min-h-16 {
1102
1109
  min-height: calc(var(--spacing) * 16);
1103
1110
  }
@@ -1179,6 +1186,9 @@
1179
1186
  .min-w-10 {
1180
1187
  min-width: calc(var(--spacing) * 10);
1181
1188
  }
1189
+ .min-w-30 {
1190
+ min-width: calc(var(--spacing) * 30);
1191
+ }
1182
1192
  .min-w-70 {
1183
1193
  min-width: calc(var(--spacing) * 70);
1184
1194
  }
@@ -1262,6 +1272,9 @@
1262
1272
  .flex-row {
1263
1273
  flex-direction: row;
1264
1274
  }
1275
+ .flex-wrap {
1276
+ flex-wrap: wrap;
1277
+ }
1265
1278
  .place-items-center {
1266
1279
  place-items: center;
1267
1280
  }
@@ -1849,24 +1862,9 @@
1849
1862
  opacity: 100%;
1850
1863
  }
1851
1864
  }
1852
- .peer-focus\:ring-2 {
1853
- &:is(:where(.peer):focus ~ *) {
1854
- --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1855
- box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1856
- }
1857
- }
1858
- .peer-focus\:ring-login\/50 {
1859
- &:is(:where(.peer):focus ~ *) {
1860
- --tw-ring-color: color-mix(in srgb, #fd8738 50%, transparent);
1861
- @supports (color: color-mix(in lab, red, red)) {
1862
- --tw-ring-color: color-mix(in oklab, var(--color-login) 50%, transparent);
1863
- }
1864
- }
1865
- }
1866
- .peer-focus\:outline-none {
1867
- &:is(:where(.peer):focus ~ *) {
1868
- --tw-outline-style: none;
1869
- outline-style: none;
1865
+ .placeholder\:text-login-200 {
1866
+ &::placeholder {
1867
+ color: var(--color-login-200);
1870
1868
  }
1871
1869
  }
1872
1870
  .after\:absolute {
@@ -1875,10 +1873,10 @@
1875
1873
  position: absolute;
1876
1874
  }
1877
1875
  }
1878
- .after\:top-0\.5 {
1876
+ .after\:top-2\.75 {
1879
1877
  &::after {
1880
1878
  content: var(--tw-content);
1881
- top: calc(var(--spacing) * 0.5);
1879
+ top: calc(var(--spacing) * 2.75);
1882
1880
  }
1883
1881
  }
1884
1882
  .after\:left-0\.5 {
@@ -1965,6 +1963,32 @@
1965
1963
  background-color: var(--color-login);
1966
1964
  }
1967
1965
  }
1966
+ .focus-within\:border-login {
1967
+ &:focus-within {
1968
+ border-color: var(--color-login);
1969
+ }
1970
+ }
1971
+ .focus-within\:border-red-500 {
1972
+ &:focus-within {
1973
+ border-color: var(--color-red-500);
1974
+ }
1975
+ }
1976
+ .focus-within\:ring-1 {
1977
+ &:focus-within {
1978
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1979
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1980
+ }
1981
+ }
1982
+ .focus-within\:ring-login {
1983
+ &:focus-within {
1984
+ --tw-ring-color: var(--color-login);
1985
+ }
1986
+ }
1987
+ .focus-within\:ring-red-500 {
1988
+ &:focus-within {
1989
+ --tw-ring-color: var(--color-red-500);
1990
+ }
1991
+ }
1968
1992
  .hover\:bg-gray-400\/10 {
1969
1993
  &:hover {
1970
1994
  @media (hover: hover) {
@@ -2053,6 +2077,13 @@
2053
2077
  }
2054
2078
  }
2055
2079
  }
2080
+ .hover\:text-red-200 {
2081
+ &:hover {
2082
+ @media (hover: hover) {
2083
+ color: var(--color-red-200);
2084
+ }
2085
+ }
2086
+ }
2056
2087
  .hover\:text-red-400 {
2057
2088
  &:hover {
2058
2089
  @media (hover: hover) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.7.9",
3
+ "version": "2.7.11",
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": {
@@ -3,6 +3,7 @@ export { default as Input } from './inputs/input'
3
3
  export { default as Textarea } from './inputs/textarea'
4
4
  export { default as Checkbox } from './inputs/checkbox'
5
5
  export { default as Select } from './inputs/select'
6
+ export { default as TagInput } from './inputs/tagInput'
6
7
  export { default as Switch } from './inputs/switch'
7
8
  export { default as Radio } from './inputs/radio'
8
9
  export { default as Range } from './inputs/range'
@@ -133,7 +133,7 @@ export default function Input({
133
133
  text-login-text placeholder-login-200
134
134
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
135
135
  disabled:opacity-50 disabled:cursor-not-allowed
136
- py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
136
+ h-10.5 py-2 ${displayIcon ? 'pl-10 pr-3' : 'px-3'}
137
137
  transition-all duration-200
138
138
  input-reset
139
139
  ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
@@ -92,7 +92,7 @@ export default function Select({
92
92
  text-login-text text-left
93
93
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
94
94
  disabled:opacity-50 disabled:cursor-not-allowed
95
- py-2 pl-3 pr-10
95
+ h-10.5 py-2 pl-3 pr-10
96
96
  transition-all duration-200
97
97
  flex items-center justify-between
98
98
  ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
@@ -34,7 +34,7 @@ export default function Switch({
34
34
  className={className}
35
35
  disabled={disabled}
36
36
  >
37
- <label className='relative inline-flex items-center cursor-pointer'>
37
+ <label className='relative inline-flex items-center cursor-pointer h-10.5'>
38
38
  <input
39
39
  type='checkbox'
40
40
  id={name}
@@ -46,13 +46,11 @@ export default function Switch({
46
46
  className='sr-only peer'
47
47
  />
48
48
  <div className={`
49
- w-11 h-6 bg-login-500/50 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-login/50
50
- rounded-full peer
49
+ w-11 h-6 bg-login-500/50 rounded-full peer
51
50
  peer-checked:after:translate-x-full peer-checked:after:border-white
52
- after:content-[''] after:absolute after:top-0.5 after:left-0.5
51
+ after:content-[''] after:absolute after:top-2.75 after:left-0.5
53
52
  after:bg-white after:border-gray-300 after:border after:rounded-full
54
- after:h-5 after:w-5 after:transition-all
55
- peer-checked:bg-login
53
+ after:h-5 after:w-5 after:transition-all peer-checked:bg-login
56
54
  ${disabled ? 'opacity-50 cursor-not-allowed' : ''}
57
55
  ${error ? 'ring-1 ring-red-500' : ''}
58
56
  `}></div>
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+
3
+ import { useState, KeyboardEvent, ChangeEvent } from 'react'
4
+ import { X } from 'lucide-react'
5
+ import { FieldWrapper } from './shared'
6
+
7
+ export type TagInputProps = {
8
+ label?: string
9
+ name: string
10
+ value?: string[]
11
+ onChange?: (value: string[]) => void
12
+ placeholder?: string
13
+ error?: string
14
+ className?: string
15
+ disabled?: boolean
16
+ required?: boolean
17
+ info?: string
18
+ }
19
+
20
+ export default function TagInput({
21
+ label,
22
+ name,
23
+ value = [],
24
+ onChange,
25
+ placeholder = 'Add...',
26
+ error,
27
+ className,
28
+ disabled,
29
+ required,
30
+ info,
31
+ }: TagInputProps) {
32
+ const [inputValue, setInputValue] = useState('')
33
+
34
+ function handleKeyDown(e: KeyboardEvent<HTMLInputElement>) {
35
+ if ((e.key === 'Enter' || e.key === ',') && inputValue.trim()) {
36
+ e.preventDefault()
37
+ const val = inputValue.trim().replace(/,$/, '')
38
+ if (val && !value.includes(val)) {
39
+ const newValue = [...value, val]
40
+ if (onChange) onChange(newValue)
41
+ }
42
+ setInputValue('')
43
+ } else if (e.key === 'Backspace' && !inputValue && value.length > 0) {
44
+ const newValue = value.slice(0, -1)
45
+ if (onChange) onChange(newValue)
46
+ }
47
+ }
48
+
49
+ function removeTag(index: number) {
50
+ if (disabled) return
51
+ const newValue = value.filter((_, i) => i !== index)
52
+ if (onChange) onChange(newValue)
53
+ }
54
+
55
+ return (
56
+ <FieldWrapper
57
+ label={label}
58
+ name={name}
59
+ required={required}
60
+ info={info}
61
+ error={error}
62
+ className={className}
63
+ >
64
+ <div
65
+ className={`
66
+ flex flex-wrap gap-2 p-2 rounded-md bg-login-500/50 border border-login-500
67
+ text-login-text min-h-10.5
68
+ focus-within:border-login focus-within:ring-1 focus-within:ring-login
69
+ disabled:opacity-50 disabled:cursor-not-allowed
70
+ transition-all duration-200
71
+ ${error ? 'border-red-500 focus-within:border-red-500 focus-within:ring-red-500' : ''}
72
+ `}
73
+ >
74
+ {value.map((tag, index) => (
75
+ <span
76
+ key={index}
77
+ className='flex items-center gap-1 px-2 py-1 bg-login rounded text-sm text-white'
78
+ >
79
+ {tag}
80
+ {!disabled && (
81
+ <button
82
+ type='button'
83
+ onClick={() => removeTag(index)}
84
+ className='hover:text-red-200 transition-colors'
85
+ >
86
+ <X size={14} />
87
+ </button>
88
+ )}
89
+ </span>
90
+ ))}
91
+ <input
92
+ type='text'
93
+ value={inputValue}
94
+ required={required && value.length === 0}
95
+ onChange={(e: ChangeEvent<HTMLInputElement>) => setInputValue(e.target.value)}
96
+ onKeyDown={handleKeyDown}
97
+ disabled={disabled}
98
+ placeholder={value.length === 0 ? placeholder : ''}
99
+ className='flex-1 bg-transparent outline-none min-w-30 text-login-text placeholder:text-login-200'
100
+ />
101
+ </div>
102
+ <input
103
+ type='hidden'
104
+ name={name}
105
+ value={value.join(',')}
106
+ />
107
+ </FieldWrapper>
108
+ )
109
+ }