uibee 2.10.5 → 2.12.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 (32) hide show
  1. package/dist/src/components/inputs/checkbox.d.ts +9 -1
  2. package/dist/src/components/inputs/checkbox.js +21 -3
  3. package/dist/src/components/inputs/input.d.ts +1 -0
  4. package/dist/src/components/inputs/input.js +2 -2
  5. package/dist/src/components/inputs/radio.d.ts +9 -1
  6. package/dist/src/components/inputs/radio.js +10 -3
  7. package/dist/src/components/inputs/range.d.ts +1 -0
  8. package/dist/src/components/inputs/range.js +2 -2
  9. package/dist/src/components/inputs/select.d.ts +2 -1
  10. package/dist/src/components/inputs/select.js +2 -2
  11. package/dist/src/components/inputs/shared/fieldWrapper.d.ts +2 -1
  12. package/dist/src/components/inputs/shared/fieldWrapper.js +2 -2
  13. package/dist/src/components/inputs/shared/selectionWrapper.d.ts +2 -1
  14. package/dist/src/components/inputs/shared/selectionWrapper.js +2 -2
  15. package/dist/src/components/inputs/switch.d.ts +1 -0
  16. package/dist/src/components/inputs/switch.js +2 -2
  17. package/dist/src/components/inputs/tagInput.d.ts +2 -1
  18. package/dist/src/components/inputs/tagInput.js +2 -2
  19. package/dist/src/components/inputs/textarea.d.ts +1 -0
  20. package/dist/src/components/inputs/textarea.js +2 -2
  21. package/dist/src/globals.css +9 -0
  22. package/package.json +1 -1
  23. package/src/components/inputs/checkbox.tsx +67 -5
  24. package/src/components/inputs/input.tsx +3 -1
  25. package/src/components/inputs/radio.tsx +55 -3
  26. package/src/components/inputs/range.tsx +3 -1
  27. package/src/components/inputs/select.tsx +3 -0
  28. package/src/components/inputs/shared/fieldWrapper.tsx +7 -0
  29. package/src/components/inputs/shared/selectionWrapper.tsx +8 -1
  30. package/src/components/inputs/switch.tsx +3 -1
  31. package/src/components/inputs/tagInput.tsx +3 -0
  32. package/src/components/inputs/textarea.tsx +3 -1
@@ -1,8 +1,16 @@
1
- export type CheckboxProps = Omit<React.ComponentProps<'input'>, 'name'> & {
1
+ export type CheckboxOption = {
2
+ label: string;
3
+ value: string | number;
4
+ };
5
+ export type CheckboxProps = Omit<React.ComponentProps<'input'>, 'name' | 'onChange' | 'value'> & {
2
6
  name: string;
3
7
  label?: string;
4
8
  error?: string;
5
9
  info?: string;
10
+ description?: string;
6
11
  className?: string;
12
+ options: CheckboxOption[];
13
+ value?: (string | number)[];
14
+ onChange?: (value: (string | number)[]) => void;
7
15
  };
8
16
  export default function Checkbox(props: CheckboxProps): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Check } from 'lucide-react';
3
- import { SelectionWrapper } from './shared';
3
+ import { SelectionWrapper, FieldWrapper } from './shared';
4
4
  export default function Checkbox(props) {
5
- const { name, label, error, info, className, ...inputProps } = props;
6
- return (_jsx(SelectionWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, className: className, disabled: inputProps.disabled, children: _jsxs("div", { className: 'relative flex items-center', children: [_jsx("input", { ...inputProps, id: name, name: name, type: 'checkbox', className: `
5
+ const { options, onChange, value, label, description, error, info, name, className, ...rest } = props;
6
+ const selectedValues = Array.isArray(value) ? value : [];
7
+ return (_jsx(FieldWrapper, { label: label, name: name, required: rest.required, info: info, description: description, error: error, className: className, children: _jsx("div", { className: 'flex flex-col gap-2', children: options.map((option) => (_jsx(CheckboxItem, { name: name, value: option.value, label: option.label, checked: selectedValues.includes(option.value), disabled: rest.disabled, onChange: (e) => {
8
+ if (!onChange)
9
+ return;
10
+ const isChecked = e.target.checked;
11
+ let newValues = [...selectedValues];
12
+ if (isChecked) {
13
+ newValues.push(option.value);
14
+ }
15
+ else {
16
+ newValues = newValues.filter((v) => v !== option.value);
17
+ }
18
+ onChange(newValues);
19
+ }, className: 'mb-0' }, option.value))) }) }));
20
+ }
21
+ function CheckboxItem(props) {
22
+ const { name, label, error, info, description, className, ...inputProps } = props;
23
+ const id = inputProps.value ? `${name}-${inputProps.value}` : name;
24
+ return (_jsx(SelectionWrapper, { label: label, name: id, required: inputProps.required, info: info, description: description, error: error, className: className, disabled: inputProps.disabled, children: _jsxs("div", { className: 'relative flex items-center', children: [_jsx("input", { ...inputProps, id: id, name: name, type: 'checkbox', className: `
7
25
  peer appearance-none h-5 w-5 rounded border border-login-500 bg-login-500/50
8
26
  checked:bg-login checked:border-login
9
27
  focus:outline-none focus:ring-2 focus:ring-login/50
@@ -6,5 +6,6 @@ export type InputProps = Omit<React.ComponentProps<'input'>, 'name'> & {
6
6
  className?: string;
7
7
  icon?: JSX.Element;
8
8
  info?: string;
9
+ description?: string;
9
10
  };
10
11
  export default function Input(props: InputProps): import("react/jsx-runtime").JSX.Element;
@@ -6,7 +6,7 @@ import DateTimePickerPopup from './shared/dateTimePickerPopup';
6
6
  import ColorPickerPopup from './shared/colorPickerPopup';
7
7
  import useClickOutside from '../../hooks/useClickOutside';
8
8
  export default function Input(props) {
9
- const { name, label, error, className, icon, info, ...inputProps } = props;
9
+ const { name, label, error, className, icon, info, description, ...inputProps } = props;
10
10
  const { type = 'text', value } = inputProps;
11
11
  const localRef = useRef(null);
12
12
  const [isOpen, setIsOpen] = useState(false);
@@ -105,7 +105,7 @@ export default function Input(props) {
105
105
  return `${dd}.${MM}.${yyyy} ${hh}:${mm}`;
106
106
  return value;
107
107
  }
108
- return (_jsx(FieldWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, className: className, children: _jsxs("div", { className: 'relative flex items-center', ref: containerRef, children: [displayIcon && (_jsx("div", { className: `
108
+ return (_jsx(FieldWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, description: description, className: className, children: _jsxs("div", { className: 'relative flex items-center', ref: containerRef, children: [displayIcon && (_jsx("div", { className: `
109
109
  absolute left-3 text-login-200
110
110
  ${isClickableType && !inputProps.disabled ? 'cursor-pointer hover:text-login-text' : 'pointer-events-none'}
111
111
  `, onClick: handleIconClick, children: displayIcon })), _jsx("input", { ...inputProps, ref: localRef, id: name, name: isClickableType ? undefined : name, type: isClickableType ? 'text' : type, value: isDateType ? getDateDisplayValue() : value, readOnly: isClickableType, onClick: () => isClickableType && !inputProps.disabled && setIsOpen(true), title: label, "aria-invalid": !!error, "aria-describedby": error ? `${name}-error` : undefined, className: `
@@ -1,8 +1,16 @@
1
- export type RadioProps = Omit<React.ComponentProps<'input'>, 'name'> & {
1
+ export type RadioOption = {
2
+ label: string;
3
+ value: string | number;
4
+ };
5
+ export type RadioProps = Omit<React.ComponentProps<'input'>, 'name' | 'onChange' | 'value'> & {
2
6
  name: string;
3
7
  label?: string;
4
8
  error?: string;
5
9
  info?: string;
10
+ description?: string;
6
11
  className?: string;
12
+ options: RadioOption[];
13
+ value?: string | number | null;
14
+ onChange?: (value: string | number) => void;
7
15
  };
8
16
  export default function Radio(props: RadioProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { SelectionWrapper } from './shared';
2
+ import { SelectionWrapper, FieldWrapper } from './shared';
3
3
  export default function Radio(props) {
4
- const { name, label, error, info, className, ...inputProps } = props;
4
+ const { options, onChange, value, label, description, error, info, name, className, ...rest } = props;
5
+ return (_jsx(FieldWrapper, { label: label, name: name, required: rest.required, info: info, description: description, error: error, className: className, children: _jsx("div", { className: 'flex flex-col gap-2', children: options.map((option) => (_jsx(RadioItem, { name: name, value: option.value, label: option.label, checked: value === option.value, disabled: rest.disabled, onChange: () => {
6
+ if (onChange)
7
+ onChange(option.value);
8
+ }, className: 'mb-0' }, option.value))) }) }));
9
+ }
10
+ function RadioItem(props) {
11
+ const { name, label, error, info, description, className, ...inputProps } = props;
5
12
  const { value } = inputProps;
6
13
  const id = `${name}-${value}`;
7
- return (_jsx(SelectionWrapper, { label: label, name: id, required: inputProps.required, info: info, error: error, className: className, disabled: inputProps.disabled, children: _jsxs("div", { className: 'relative flex items-center', children: [_jsx("input", { ...inputProps, id: id, name: name, type: 'radio', className: `
14
+ return (_jsx(SelectionWrapper, { label: label, name: id, required: inputProps.required, info: info, description: description, error: error, className: className, disabled: inputProps.disabled, children: _jsxs("div", { className: 'relative flex items-center', children: [_jsx("input", { ...inputProps, id: id, name: name, type: 'radio', className: `
8
15
  peer appearance-none h-5 w-5 rounded-full border border-login-500 bg-login-500/50
9
16
  checked:bg-login checked:border-login
10
17
  focus:outline-none focus:ring-2 focus:ring-login/50
@@ -4,6 +4,7 @@ export type RangeProps = Omit<React.ComponentProps<'input'>, 'name'> & {
4
4
  error?: string;
5
5
  className?: string;
6
6
  info?: string;
7
+ description?: string;
7
8
  showValue?: boolean;
8
9
  };
9
10
  export default function Range(props: RangeProps): import("react/jsx-runtime").JSX.Element;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { FieldWrapper } from './shared';
3
3
  export default function Range(props) {
4
- const { name, label, error, className, info, showValue = true, ...inputProps } = props;
4
+ const { name, label, error, className, info, description, showValue = true, ...inputProps } = props;
5
5
  const { min = 0, max = 100, step = 1, value = 0 } = inputProps;
6
- return (_jsx(FieldWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, className: className, children: _jsxs("div", { className: 'flex items-center gap-4', children: [_jsx("input", { ...inputProps, id: name, name: name, type: 'range', min: min, max: max, step: step, value: value, title: label, "aria-invalid": !!error, "aria-describedby": error ? `${name}-error` : undefined, className: `
6
+ return (_jsx(FieldWrapper, { label: label, name: name, required: inputProps.required, info: info, description: description, error: error, className: className, children: _jsxs("div", { className: 'flex items-center gap-4', children: [_jsx("input", { ...inputProps, id: name, name: name, type: 'range', min: min, max: max, step: step, value: value, title: label, "aria-invalid": !!error, "aria-describedby": error ? `${name}-error` : undefined, className: `
7
7
  flex-1 h-2 bg-login-500 rounded-lg appearance-none cursor-pointer
8
8
  accent-login
9
9
  [&::-webkit-slider-thumb]:appearance-none
@@ -15,7 +15,8 @@ export type SelectProps = {
15
15
  required?: boolean;
16
16
  placeholder?: string;
17
17
  info?: string;
18
+ description?: string;
18
19
  clearable?: boolean;
19
20
  searchable?: boolean;
20
21
  };
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;
22
+ export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder, info, description, clearable, searchable, }: SelectProps): import("react/jsx-runtime").JSX.Element;
@@ -5,7 +5,7 @@ import Image from 'next/image';
5
5
  import { useClickOutside } from '../../hooks';
6
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, searchable = true, }) {
8
+ export default function Select({ label, name, value, onChange, options, error, className, disabled, required, placeholder = 'Select an option', info, description, clearable = true, searchable = true, }) {
9
9
  const [isOpen, setIsOpen] = useState(false);
10
10
  const [searchTerm, setSearchTerm] = useState('');
11
11
  const [selectedOption, setSelectedOption] = useState(options.find(opt => opt.value === value));
@@ -37,7 +37,7 @@ export default function Select({ label, name, value, onChange, options, error, c
37
37
  }
38
38
  }
39
39
  const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchTerm.toLowerCase()));
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: `
40
+ return (_jsxs(FieldWrapper, { label: label, name: name, required: required, info: info, description: description, 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: `
41
41
  w-full rounded-md bg-login-500/50 border border-login-500
42
42
  text-login-text text-left
43
43
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
@@ -5,8 +5,9 @@ interface FieldWrapperProps {
5
5
  required?: boolean;
6
6
  info?: string;
7
7
  error?: string;
8
+ description?: string;
8
9
  children: ReactNode;
9
10
  className?: string;
10
11
  }
11
- export default function FieldWrapper({ label, name, required, info, error, children, className, }: FieldWrapperProps): import("react/jsx-runtime").JSX.Element;
12
+ export default function FieldWrapper({ label, name, required, info, error, description, children, className, }: FieldWrapperProps): import("react/jsx-runtime").JSX.Element;
12
13
  export {};
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import InputLabel from './inputLabel';
3
3
  import InputInfo from './inputInfo';
4
4
  import InputError from './inputError';
5
- export default function FieldWrapper({ label, name, required, info, error, children, className, }) {
6
- return (_jsxs("div", { className: `flex flex-col gap-1 w-full relative ${className || ''}`, children: [(label || info) && (_jsxs("div", { className: 'flex items-center justify-between mb-1', children: [label && (_jsx(InputLabel, { label: label, name: name, required: required, className: 'ml-1' })), info && _jsx(InputInfo, { info: info })] })), children, _jsx(InputError, { error: error, id: `${name}-error` })] }));
5
+ export default function FieldWrapper({ label, name, required, info, error, description, children, className, }) {
6
+ return (_jsxs("div", { className: `flex flex-col gap-1 w-full relative ${className || ''}`, children: [(label || info) && (_jsxs("div", { className: 'flex items-center justify-between mb-1', children: [label && (_jsx(InputLabel, { label: label, name: name, required: required, className: 'ml-1' })), info && _jsx(InputInfo, { info: info })] })), description && (_jsx("p", { className: 'text-sm text-login-100 ml-1 mb-1', children: description })), children, _jsx(InputError, { error: error, id: `${name}-error` })] }));
7
7
  }
@@ -5,10 +5,11 @@ interface SelectionWrapperProps {
5
5
  required?: boolean;
6
6
  info?: string;
7
7
  error?: string;
8
+ description?: string;
8
9
  children: ReactNode;
9
10
  className?: string;
10
11
  disabled?: boolean;
11
12
  hideError?: boolean;
12
13
  }
13
- export default function SelectionWrapper({ label, name, required, info, error, children, className, disabled, hideError, }: SelectionWrapperProps): import("react/jsx-runtime").JSX.Element;
14
+ export default function SelectionWrapper({ label, name, required, info, error, description, children, className, disabled, hideError, }: SelectionWrapperProps): import("react/jsx-runtime").JSX.Element;
14
15
  export {};
@@ -2,6 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import InputLabel from './inputLabel';
3
3
  import InputInfo from './inputInfo';
4
4
  import InputError from './inputError';
5
- export default function SelectionWrapper({ label, name, required, info, error, children, className, disabled, hideError, }) {
6
- return (_jsxs("div", { className: `flex flex-col gap-1 ${className || ''}`, children: [_jsxs("div", { className: 'flex items-center justify-between mb-1', children: [_jsxs("div", { className: 'flex items-center gap-2', children: [children, label && (_jsx(InputLabel, { label: label, name: name, required: required, disabled: disabled, className: 'select-none cursor-pointer' }))] }), info && _jsx(InputInfo, { info: info })] }), !hideError && _jsx(InputError, { error: error })] }));
5
+ export default function SelectionWrapper({ label, name, required, info, error, description, children, className, disabled, hideError, }) {
6
+ return (_jsxs("div", { className: `flex flex-col gap-1 ${className || ''}`, children: [_jsxs("div", { className: 'flex items-start justify-between mb-1', children: [_jsxs("div", { className: 'flex items-center gap-2', children: [children, label && (_jsx(InputLabel, { label: label, name: name, required: required, disabled: disabled, className: 'select-none cursor-pointer' }))] }), info && _jsx(InputInfo, { info: info })] }), description && (_jsx("p", { className: 'text-xs text-login-200 ml-7 -mt-1 mb-1', children: description })), !hideError && _jsx(InputError, { error: error })] }));
7
7
  }
@@ -3,6 +3,7 @@ export type SwitchProps = Omit<React.ComponentProps<'input'>, 'name'> & {
3
3
  label?: string;
4
4
  error?: string;
5
5
  info?: string;
6
+ description?: string;
6
7
  className?: string;
7
8
  switchOnly?: boolean;
8
9
  };
@@ -1,8 +1,8 @@
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(props) {
4
- const { name, label, error, info, className, switchOnly, ...inputProps } = props;
5
- return (_jsx(SelectionWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, hideError: switchOnly, className: className, disabled: inputProps.disabled, children: _jsxs("label", { className: `relative inline-flex items-center cursor-pointer ${switchOnly ? 'h-fit' : 'h-10.5'}`, children: [_jsx("input", { ...inputProps, type: 'checkbox', id: name, name: name, className: 'sr-only peer' }), _jsx("div", { className: `
4
+ const { name, label, error, info, description, className, switchOnly, ...inputProps } = props;
5
+ return (_jsx(SelectionWrapper, { label: label, name: name, required: inputProps.required, info: info, description: description, error: error, hideError: switchOnly, className: className, disabled: inputProps.disabled, children: _jsxs("label", { className: `relative inline-flex items-center cursor-pointer ${switchOnly ? 'h-fit' : 'h-10.5'}`, children: [_jsx("input", { ...inputProps, type: 'checkbox', id: name, name: name, className: 'sr-only peer' }), _jsx("div", { className: `
6
6
  w-11 h-6 bg-login-500/50 rounded-full peer
7
7
  peer-checked:after:translate-x-full peer-checked:after:border-white
8
8
  after:content-[''] after:absolute ${switchOnly ? 'after:top-0.5' : 'after:top-2.75'} after:left-0.5
@@ -9,5 +9,6 @@ export type TagInputProps = {
9
9
  disabled?: boolean;
10
10
  required?: boolean;
11
11
  info?: string;
12
+ description?: string;
12
13
  };
13
- export default function TagInput({ label, name, value, onChange, placeholder, error, className, disabled, required, info, }: TagInputProps): import("react/jsx-runtime").JSX.Element;
14
+ export default function TagInput({ label, name, value, onChange, placeholder, error, className, disabled, required, info, description, }: TagInputProps): import("react/jsx-runtime").JSX.Element;
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState } from 'react';
4
4
  import { X } from 'lucide-react';
5
5
  import { FieldWrapper } from './shared';
6
- export default function TagInput({ label, name, value = [], onChange, placeholder = 'Add...', error, className, disabled, required, info, }) {
6
+ export default function TagInput({ label, name, value = [], onChange, placeholder = 'Add...', error, className, disabled, required, info, description, }) {
7
7
  const [inputValue, setInputValue] = useState('');
8
8
  function handleKeyDown(e) {
9
9
  if ((e.key === 'Enter' || e.key === ',') && inputValue.trim()) {
@@ -29,7 +29,7 @@ export default function TagInput({ label, name, value = [], onChange, placeholde
29
29
  if (onChange)
30
30
  onChange(newValue);
31
31
  }
32
- return (_jsxs(FieldWrapper, { label: label, name: name, required: required, info: info, error: error, className: className, children: [_jsxs("div", { className: `
32
+ return (_jsxs(FieldWrapper, { label: label, name: name, required: required, info: info, description: description, error: error, className: className, children: [_jsxs("div", { className: `
33
33
  flex flex-wrap gap-2 p-2 rounded-md bg-login-500/50 border border-login-500
34
34
  text-login-text min-h-10.5
35
35
  focus-within:border-login focus-within:ring-1 focus-within:ring-login
@@ -4,6 +4,7 @@ export type TextareaProps = Omit<React.ComponentProps<'textarea'>, 'name'> & {
4
4
  error?: string;
5
5
  className?: string;
6
6
  info?: string;
7
+ description?: string;
7
8
  type?: 'markdown' | 'json' | 'text';
8
9
  };
9
10
  export default function Textarea(props: TextareaProps): import("react/jsx-runtime").JSX.Element;
@@ -13,12 +13,12 @@ function isValidJson(str) {
13
13
  }
14
14
  }
15
15
  export default function Textarea(props) {
16
- const { name, label, error, className, info, type = 'text', rows = 4, ...textareaProps } = props;
16
+ const { name, label, error, className, info, description, type = 'text', rows = 4, ...textareaProps } = props;
17
17
  const { value } = textareaProps;
18
18
  const [preview, setPreview] = useState(false);
19
19
  const jsonError = type === 'json' && value ? isValidJson(value) : undefined;
20
20
  const displayError = jsonError || error;
21
- return (_jsx(FieldWrapper, { label: label, name: name, required: textareaProps.required, info: info, error: displayError, className: className, children: _jsxs("div", { className: 'relative', children: [type === 'markdown' && (_jsx("div", { className: 'absolute right-2 top-2 z-10 flex gap-2', children: _jsx("button", { type: 'button', onClick: () => setPreview(!preview), className: 'p-1 rounded hover:bg-login-500/50 text-login-text transition-colors', title: preview ? 'Edit' : 'Preview', children: preview ? _jsx(Pencil, { size: 16 }) : _jsx(Eye, { size: 16 }) }) })), type === 'markdown' && preview ? (_jsx("div", { className: `
21
+ return (_jsx(FieldWrapper, { label: label, name: name, required: textareaProps.required, info: info, description: description, error: displayError, className: className, children: _jsxs("div", { className: 'relative', children: [type === 'markdown' && (_jsx("div", { className: 'absolute right-2 top-2 z-10 flex gap-2', children: _jsx("button", { type: 'button', onClick: () => setPreview(!preview), className: 'p-1 rounded hover:bg-login-500/50 text-login-text transition-colors', title: preview ? 'Edit' : 'Preview', children: preview ? _jsx(Pencil, { size: 16 }) : _jsx(Eye, { size: 16 }) }) })), type === 'markdown' && preview ? (_jsx("div", { className: `
22
22
  w-full rounded-md bg-login-500/50 border border-login-500
23
23
  text-login-text
24
24
  p-3
@@ -1028,6 +1028,9 @@
1028
1028
  .-mt-0\.5 {
1029
1029
  margin-top: calc(var(--spacing) * -0.5);
1030
1030
  }
1031
+ .-mt-1 {
1032
+ margin-top: calc(var(--spacing) * -1);
1033
+ }
1031
1034
  .mt-1 {
1032
1035
  margin-top: calc(var(--spacing) * 1);
1033
1036
  }
@@ -1040,6 +1043,9 @@
1040
1043
  .mr-\[0\.3rem\] {
1041
1044
  margin-right: 0.3rem;
1042
1045
  }
1046
+ .mb-0 {
1047
+ margin-bottom: calc(var(--spacing) * 0);
1048
+ }
1043
1049
  .mb-1 {
1044
1050
  margin-bottom: calc(var(--spacing) * 1);
1045
1051
  }
@@ -1058,6 +1064,9 @@
1058
1064
  .ml-1 {
1059
1065
  margin-left: calc(var(--spacing) * 1);
1060
1066
  }
1067
+ .ml-7 {
1068
+ margin-left: calc(var(--spacing) * 7);
1069
+ }
1061
1070
  .page-container {
1062
1071
  display: grid;
1063
1072
  grid-template-columns: 1fr 1rem minmax(0, 72rem) 1rem 1fr;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.10.5",
3
+ "version": "2.12.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": {
@@ -1,23 +1,85 @@
1
1
  import { Check } from 'lucide-react'
2
- import { SelectionWrapper } from './shared'
2
+ import { SelectionWrapper, FieldWrapper } from './shared'
3
3
 
4
- export type CheckboxProps = Omit<React.ComponentProps<'input'>, 'name'> & {
4
+ export type CheckboxOption = {
5
+ label: string
6
+ value: string | number
7
+ }
8
+
9
+ type CheckboxItemProps = Omit<React.ComponentProps<'input'>, 'name'> & {
5
10
  name: string
6
11
  label?: string
7
12
  error?: string
8
13
  info?: string
14
+ description?: string
9
15
  className?: string
10
16
  }
11
17
 
18
+ export type CheckboxProps = Omit<React.ComponentProps<'input'>, 'name' | 'onChange' | 'value'> & {
19
+ name: string
20
+ label?: string
21
+ error?: string
22
+ info?: string
23
+ description?: string
24
+ className?: string
25
+ options: CheckboxOption[]
26
+ value?: (string | number)[]
27
+ onChange?: (value: (string | number)[]) => void
28
+ }
29
+
12
30
  export default function Checkbox(props: CheckboxProps) {
13
- const { name, label, error, info, className, ...inputProps } = props
31
+ const { options, onChange, value, label, description, error, info, name, className, ...rest } = props
32
+ const selectedValues = Array.isArray(value) ? value : []
14
33
 
15
34
  return (
16
- <SelectionWrapper
35
+ <FieldWrapper
17
36
  label={label}
18
37
  name={name}
38
+ required={rest.required}
39
+ info={info}
40
+ description={description}
41
+ error={error}
42
+ className={className}
43
+ >
44
+ <div className='flex flex-col gap-2'>
45
+ {options.map((option) => (
46
+ <CheckboxItem
47
+ key={option.value}
48
+ name={name}
49
+ value={option.value}
50
+ label={option.label}
51
+ checked={selectedValues.includes(option.value)}
52
+ disabled={rest.disabled}
53
+ onChange={(e) => {
54
+ if (!onChange) return
55
+ const isChecked = e.target.checked
56
+ let newValues = [...selectedValues]
57
+ if (isChecked) {
58
+ newValues.push(option.value)
59
+ } else {
60
+ newValues = newValues.filter((v) => v !== option.value)
61
+ }
62
+ onChange(newValues)
63
+ }}
64
+ className='mb-0'
65
+ />
66
+ ))}
67
+ </div>
68
+ </FieldWrapper>
69
+ )
70
+ }
71
+
72
+ function CheckboxItem(props: CheckboxItemProps) {
73
+ const { name, label, error, info, description, className, ...inputProps } = props
74
+ const id = inputProps.value ? `${name}-${inputProps.value}` : name
75
+
76
+ return (
77
+ <SelectionWrapper
78
+ label={label}
79
+ name={id}
19
80
  required={inputProps.required}
20
81
  info={info}
82
+ description={description}
21
83
  error={error}
22
84
  className={className}
23
85
  disabled={inputProps.disabled}
@@ -25,7 +87,7 @@ export default function Checkbox(props: CheckboxProps) {
25
87
  <div className='relative flex items-center'>
26
88
  <input
27
89
  {...inputProps}
28
- id={name}
90
+ id={id}
29
91
  name={name}
30
92
  type='checkbox'
31
93
  className={`
@@ -12,10 +12,11 @@ export type InputProps = Omit<React.ComponentProps<'input'>, 'name'> & {
12
12
  className?: string
13
13
  icon?: JSX.Element
14
14
  info?: string
15
+ description?: string
15
16
  }
16
17
 
17
18
  export default function Input(props: InputProps) {
18
- const { name, label, error, className, icon, info, ...inputProps } = props
19
+ const { name, label, error, className, icon, info, description, ...inputProps } = props
19
20
  const { type = 'text', value } = inputProps
20
21
  const localRef = useRef<HTMLInputElement>(null)
21
22
  const [isOpen, setIsOpen] = useState(false)
@@ -130,6 +131,7 @@ export default function Input(props: InputProps) {
130
131
  required={inputProps.required}
131
132
  info={info}
132
133
  error={error}
134
+ description={description}
133
135
  className={className}
134
136
  >
135
137
  <div className='relative flex items-center' ref={containerRef}>
@@ -1,15 +1,66 @@
1
- import { SelectionWrapper } from './shared'
1
+ import { SelectionWrapper, FieldWrapper } from './shared'
2
2
 
3
- export type RadioProps = Omit<React.ComponentProps<'input'>, 'name'> & {
3
+ export type RadioOption = {
4
+ label: string
5
+ value: string | number
6
+ }
7
+
8
+ type RadioItemProps = Omit<React.ComponentProps<'input'>, 'name'> & {
9
+ name: string
10
+ label?: string
11
+ error?: string
12
+ info?: string
13
+ description?: string
14
+ className?: string
15
+ }
16
+
17
+ export type RadioProps = Omit<React.ComponentProps<'input'>, 'name' | 'onChange' | 'value'> & {
4
18
  name: string
5
19
  label?: string
6
20
  error?: string
7
21
  info?: string
22
+ description?: string
8
23
  className?: string
24
+ options: RadioOption[]
25
+ value?: string | number | null
26
+ onChange?: (value: string | number) => void
9
27
  }
10
28
 
11
29
  export default function Radio(props: RadioProps) {
12
- const { name, label, error, info, className, ...inputProps } = props
30
+ const { options, onChange, value, label, description, error, info, name, className, ...rest } = props
31
+
32
+ return (
33
+ <FieldWrapper
34
+ label={label}
35
+ name={name}
36
+ required={rest.required}
37
+ info={info}
38
+ description={description}
39
+ error={error}
40
+ className={className}
41
+ >
42
+ <div className='flex flex-col gap-2'>
43
+ {options.map((option) => (
44
+ <RadioItem
45
+ key={option.value}
46
+ name={name}
47
+ value={option.value}
48
+ label={option.label}
49
+ checked={value === option.value}
50
+ disabled={rest.disabled}
51
+ onChange={() => {
52
+ if (onChange) onChange(option.value)
53
+ }}
54
+ className='mb-0'
55
+ />
56
+ ))}
57
+ </div>
58
+ </FieldWrapper>
59
+ )
60
+ }
61
+
62
+ function RadioItem(props: RadioItemProps) {
63
+ const { name, label, error, info, description, className, ...inputProps } = props
13
64
  const { value } = inputProps
14
65
  const id = `${name}-${value}`
15
66
 
@@ -19,6 +70,7 @@ export default function Radio(props: RadioProps) {
19
70
  name={id}
20
71
  required={inputProps.required}
21
72
  info={info}
73
+ description={description}
22
74
  error={error}
23
75
  className={className}
24
76
  disabled={inputProps.disabled}
@@ -6,11 +6,12 @@ export type RangeProps = Omit<React.ComponentProps<'input'>, 'name'> & {
6
6
  error?: string
7
7
  className?: string
8
8
  info?: string
9
+ description?: string
9
10
  showValue?: boolean
10
11
  }
11
12
 
12
13
  export default function Range(props: RangeProps) {
13
- const { name, label, error, className, info, showValue = true, ...inputProps } = props
14
+ const { name, label, error, className, info, description, showValue = true, ...inputProps } = props
14
15
  const { min = 0, max = 100, step = 1, value = 0 } = inputProps
15
16
 
16
17
  return (
@@ -19,6 +20,7 @@ export default function Range(props: RangeProps) {
19
20
  name={name}
20
21
  required={inputProps.required}
21
22
  info={info}
23
+ description={description}
22
24
  error={error}
23
25
  className={className}
24
26
  >
@@ -24,6 +24,7 @@ export type SelectProps = {
24
24
  required?: boolean
25
25
  placeholder?: string
26
26
  info?: string
27
+ description?: string
27
28
  clearable?: boolean
28
29
  searchable?: boolean
29
30
  }
@@ -40,6 +41,7 @@ export default function Select({
40
41
  required,
41
42
  placeholder = 'Select an option',
42
43
  info,
44
+ description,
43
45
  clearable = true,
44
46
  searchable = true,
45
47
  }: SelectProps) {
@@ -89,6 +91,7 @@ export default function Select({
89
91
  name={name}
90
92
  required={required}
91
93
  info={info}
94
+ description={description}
92
95
  error={error}
93
96
  className={className}
94
97
  >
@@ -9,6 +9,7 @@ interface FieldWrapperProps {
9
9
  required?: boolean
10
10
  info?: string
11
11
  error?: string
12
+ description?: string
12
13
  children: ReactNode
13
14
  className?: string
14
15
  }
@@ -19,6 +20,7 @@ export default function FieldWrapper({
19
20
  required,
20
21
  info,
21
22
  error,
23
+ description,
22
24
  children,
23
25
  className,
24
26
  }: FieldWrapperProps) {
@@ -37,6 +39,11 @@ export default function FieldWrapper({
37
39
  {info && <InputInfo info={info} />}
38
40
  </div>
39
41
  )}
42
+ {description && (
43
+ <p className='text-sm text-login-100 ml-1 mb-1'>
44
+ {description}
45
+ </p>
46
+ )}
40
47
  {children}
41
48
  <InputError error={error} id={`${name}-error`} />
42
49
  </div>
@@ -9,6 +9,7 @@ interface SelectionWrapperProps {
9
9
  required?: boolean
10
10
  info?: string
11
11
  error?: string
12
+ description?: string
12
13
  children: ReactNode
13
14
  className?: string
14
15
  disabled?: boolean
@@ -21,6 +22,7 @@ export default function SelectionWrapper({
21
22
  required,
22
23
  info,
23
24
  error,
25
+ description,
24
26
  children,
25
27
  className,
26
28
  disabled,
@@ -28,7 +30,7 @@ export default function SelectionWrapper({
28
30
  }: SelectionWrapperProps) {
29
31
  return (
30
32
  <div className={`flex flex-col gap-1 ${className || ''}`}>
31
- <div className='flex items-center justify-between mb-1'>
33
+ <div className='flex items-start justify-between mb-1'>
32
34
  <div className='flex items-center gap-2'>
33
35
  {children}
34
36
  {label && (
@@ -43,6 +45,11 @@ export default function SelectionWrapper({
43
45
  </div>
44
46
  {info && <InputInfo info={info} />}
45
47
  </div>
48
+ {description && (
49
+ <p className='text-xs text-login-200 ml-7 -mt-1 mb-1'>
50
+ {description}
51
+ </p>
52
+ )}
46
53
  {!hideError && <InputError error={error} />}
47
54
  </div>
48
55
  )
@@ -5,12 +5,13 @@ export type SwitchProps = Omit<React.ComponentProps<'input'>, 'name'> & {
5
5
  label?: string
6
6
  error?: string
7
7
  info?: string
8
+ description?: string
8
9
  className?: string
9
10
  switchOnly?: boolean
10
11
  }
11
12
 
12
13
  export default function Switch(props: SwitchProps) {
13
- const { name, label, error, info, className, switchOnly, ...inputProps } = props
14
+ const { name, label, error, info, description, className, switchOnly, ...inputProps } = props
14
15
 
15
16
  return (
16
17
  <SelectionWrapper
@@ -18,6 +19,7 @@ export default function Switch(props: SwitchProps) {
18
19
  name={name}
19
20
  required={inputProps.required}
20
21
  info={info}
22
+ description={description}
21
23
  error={error}
22
24
  hideError={switchOnly}
23
25
  className={className}
@@ -15,6 +15,7 @@ export type TagInputProps = {
15
15
  disabled?: boolean
16
16
  required?: boolean
17
17
  info?: string
18
+ description?: string
18
19
  }
19
20
 
20
21
  export default function TagInput({
@@ -28,6 +29,7 @@ export default function TagInput({
28
29
  disabled,
29
30
  required,
30
31
  info,
32
+ description,
31
33
  }: TagInputProps) {
32
34
  const [inputValue, setInputValue] = useState('')
33
35
 
@@ -58,6 +60,7 @@ export default function TagInput({
58
60
  name={name}
59
61
  required={required}
60
62
  info={info}
63
+ description={description}
61
64
  error={error}
62
65
  className={className}
63
66
  >
@@ -9,6 +9,7 @@ export type TextareaProps = Omit<React.ComponentProps<'textarea'>, 'name'> & {
9
9
  error?: string
10
10
  className?: string
11
11
  info?: string
12
+ description?: string
12
13
  type?: 'markdown' | 'json' | 'text'
13
14
  }
14
15
 
@@ -22,7 +23,7 @@ function isValidJson(str: string): string | null {
22
23
  }
23
24
 
24
25
  export default function Textarea(props: TextareaProps) {
25
- const { name, label, error, className, info, type = 'text', rows = 4, ...textareaProps } = props
26
+ const { name, label, error, className, info, description, type = 'text', rows = 4, ...textareaProps } = props
26
27
  const { value } = textareaProps
27
28
  const [preview, setPreview] = useState(false)
28
29
 
@@ -35,6 +36,7 @@ export default function Textarea(props: TextareaProps) {
35
36
  name={name}
36
37
  required={textareaProps.required}
37
38
  info={info}
39
+ description={description}
38
40
  error={displayError}
39
41
  className={className}
40
42
  >