uibee 2.16.35 → 2.16.37

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.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef, useState } from 'react';
2
+ import { useRef, useState, useId } from 'react';
3
3
  import { Calendar, Clock } from 'lucide-react';
4
4
  import { FieldWrapper } from './shared';
5
5
  import DateTimePickerPopup from './shared/dateTimePickerPopup';
@@ -10,6 +10,8 @@ export default function Input(props) {
10
10
  const { type = 'text', value } = inputProps;
11
11
  const localRef = useRef(null);
12
12
  const [isOpen, setIsOpen] = useState(false);
13
+ const id = useId();
14
+ const anchorName = `--input-${id.replace(/:/g, '')}`;
13
15
  const containerRef = useClickOutside(() => setIsOpen(false));
14
16
  const isDateType = ['date', 'datetime-local', 'time'].includes(type);
15
17
  const isColorType = type === 'color';
@@ -108,7 +110,7 @@ export default function Input(props) {
108
110
  return (_jsx(FieldWrapper, { label: label, name: name, required: inputProps.required, info: info, error: error, description: description, textSize: textSize, className: className, children: _jsxs("div", { className: 'relative flex items-center', ref: containerRef, children: [displayIcon && (_jsx("div", { className: `
109
111
  absolute left-3 text-login-200
110
112
  ${isClickableType && !inputProps.disabled ? 'cursor-pointer hover:text-login-text' : 'pointer-events-none'}
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: `
113
+ `, 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), "aria-describedby": error ? `${name}-error` : undefined, style: { anchorName }, className: `
112
114
  w-full rounded-md bg-login-500/50 border border-login-500
113
115
  text-login-text placeholder-login-200
114
116
  focus:outline-none focus:border-login focus:ring-1 focus:ring-login
@@ -118,5 +120,5 @@ export default function Input(props) {
118
120
  input-reset
119
121
  ${error ? 'border-red-500 focus:border-red-500 focus:ring-red-500' : ''}
120
122
  ${isClickableType && !inputProps.disabled ? 'cursor-pointer' : ''}
121
- ` }), isClickableType && (_jsx("input", { type: 'hidden', name: name, value: value })), isOpen && isDateType && !inputProps.disabled && (_jsx(DateTimePickerPopup, { value: getDateValue(), onChange: handleDateChange, type: type, onClose: () => setIsOpen(false) })), isOpen && isColorType && !inputProps.disabled && (_jsx(ColorPickerPopup, { value: value || '', onChange: handleColorChange, onClose: () => setIsOpen(false) }))] }) }));
123
+ ` }), isClickableType && (_jsx("input", { type: 'hidden', name: name, value: value })), isOpen && isDateType && !inputProps.disabled && (_jsx(DateTimePickerPopup, { value: getDateValue(), onChange: handleDateChange, type: type, onClose: () => setIsOpen(false), anchorName: anchorName })), isOpen && isColorType && !inputProps.disabled && (_jsx(ColorPickerPopup, { value: value || '', onChange: handleColorChange, onClose: () => setIsOpen(false), anchorName: anchorName }))] }) }));
122
124
  }
@@ -3,5 +3,6 @@ export type ColorPickerPopupProps = {
3
3
  value: string;
4
4
  onChange: (color: string) => void;
5
5
  onClose: () => void;
6
+ anchorName?: string;
6
7
  };
7
- export default function ColorPickerPopup({ value, onChange, onClose }: ColorPickerPopupProps): JSX.Element;
8
+ export default function ColorPickerPopup({ value, onChange, onClose, anchorName }: ColorPickerPopupProps): JSX.Element;
@@ -142,7 +142,7 @@ function HuePicker({ hue, onChange }) {
142
142
  top-1/2 pointer-events-none
143
143
  `, style: { left: `${(hue / 360) * 100}%` } }) }));
144
144
  }
145
- export default function ColorPickerPopup({ value, onChange, onClose }) {
145
+ export default function ColorPickerPopup({ value, onChange, onClose, anchorName }) {
146
146
  const [hsv, setHsv] = useState(() => hexToHsv(value || '#000000'));
147
147
  const [hexInput, setHexInput] = useState(value || '#000000');
148
148
  useEffect(() => {
@@ -168,7 +168,13 @@ export default function ColorPickerPopup({ value, onChange, onClose }) {
168
168
  onChange(val);
169
169
  }
170
170
  }
171
- return (_jsxs("div", { className: 'absolute top-full left-0 mt-1 z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-3 w-64 select-none', children: [_jsx(SaturationPicker, { hsv: hsv, onChange: handleSaturationChange }), _jsx(HuePicker, { hue: hsv.h, onChange: handleHueChange }), _jsxs("div", { className: 'flex items-center gap-2 mb-3', children: [_jsx("div", { className: 'text-xs text-login-200 font-mono', children: "HEX" }), _jsx("input", { type: 'text', value: hexInput, onChange: manualHexChange, className: `
171
+ return (_jsxs("div", { className: 'fixed z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-3 w-64 select-none anchor-popup', style: {
172
+ positionAnchor: anchorName,
173
+ positionArea: 'bottom span-right',
174
+ insetArea: 'bottom span-right',
175
+ positionTryFallbacks: 'flip-block',
176
+ margin: '0.25rem 0',
177
+ }, children: [_jsx(SaturationPicker, { hsv: hsv, onChange: handleSaturationChange }), _jsx(HuePicker, { hue: hsv.h, onChange: handleHueChange }), _jsxs("div", { className: 'flex items-center gap-2 mb-3', children: [_jsx("div", { className: 'text-xs text-login-200 font-mono', children: "HEX" }), _jsx("input", { type: 'text', value: hexInput, onChange: manualHexChange, className: `
172
178
  flex-1 min-w-0 bg-login-500 border border-login-500 rounded
173
179
  px-2 py-1 text-sm text-login-text focus:outline-none
174
180
  focus:border-login focus:ring-1 focus:ring-login
@@ -3,6 +3,7 @@ type DateTimePickerPopupProps = {
3
3
  onChange: (date: Date) => void;
4
4
  type: 'date' | 'time' | 'datetime-local';
5
5
  onClose?: () => void;
6
+ anchorName?: string;
6
7
  };
7
- export default function DateTimePickerPopup({ value, onChange, type, onClose, }: DateTimePickerPopupProps): import("react/jsx-runtime").JSX.Element;
8
+ export default function DateTimePickerPopup({ value, onChange, type, onClose, anchorName, }: DateTimePickerPopupProps): import("react/jsx-runtime").JSX.Element;
8
9
  export {};
@@ -6,7 +6,7 @@ const MONTHS = [
6
6
  'January', 'February', 'March', 'April', 'May', 'June',
7
7
  'July', 'August', 'September', 'October', 'November', 'December'
8
8
  ];
9
- export default function DateTimePickerPopup({ value, onChange, type, onClose, }) {
9
+ export default function DateTimePickerPopup({ value, onChange, type, onClose, anchorName, }) {
10
10
  const [currentDate, setCurrentDate] = useState(new Date());
11
11
  const [timeInput, setTimeInput] = useState({
12
12
  hours: value ? value.getHours().toString() : '0',
@@ -118,5 +118,11 @@ export default function DateTimePickerPopup({ value, onChange, type, onClose, })
118
118
  border border-login-500 focus:border-login outline-none
119
119
  ` })] })] }));
120
120
  }
121
- return (_jsxs("div", { className: 'absolute top-full left-0 z-50 mt-1 bg-login-600 border border-login-500 rounded-md shadow-lg p-1 min-w-70', children: [type !== 'time' && renderCalendar(), (type === 'time' || type === 'datetime-local') && renderTimePicker()] }));
121
+ return (_jsxs("div", { className: 'fixed z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-1 min-w-70 anchor-popup', style: {
122
+ positionAnchor: anchorName,
123
+ positionArea: 'bottom span-right',
124
+ insetArea: 'bottom span-right',
125
+ positionTryFallbacks: 'flip-block',
126
+ margin: '0.25rem 0',
127
+ }, children: [type !== 'time' && renderCalendar(), (type === 'time' || type === 'datetime-local') && renderTimePicker()] }));
122
128
  }
@@ -2,13 +2,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChevronDown, ChevronUp } from 'lucide-react';
3
3
  import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4
4
  import { useEffect, useState } from 'react';
5
+ function parseOrder(value) {
6
+ return value === 'asc' || value === 'desc' ? value : undefined;
7
+ }
5
8
  export default function Header({ columns, hideMenu, variant = 'default' }) {
6
- const [column, setColumn] = useState(columns[0]?.key || '');
7
- const [order, setOrder] = useState('asc');
8
9
  const router = useRouter();
9
10
  const pathname = usePathname();
10
11
  const searchParams = useSearchParams();
12
+ const [column, setColumn] = useState(searchParams.get('column') ?? '');
13
+ const [order, setOrder] = useState(parseOrder(searchParams.get('order')));
11
14
  useEffect(() => {
15
+ if (!column || !order) {
16
+ return;
17
+ }
12
18
  const params = new URLSearchParams(searchParams.toString());
13
19
  if (searchParams.get('order') !== order ||
14
20
  searchParams.get('column') !== column) {
@@ -333,6 +333,14 @@
333
333
  .end {
334
334
  inset-inline-end: var(--spacing);
335
335
  }
336
+ .anchor-popup {
337
+ top: 100%;
338
+ left: 0;
339
+ @supports (position-anchor: --foo) {
340
+ top: auto;
341
+ left: auto;
342
+ }
343
+ }
336
344
  .-top-3 {
337
345
  top: calc(var(--spacing) * -3);
338
346
  }
@@ -360,9 +368,6 @@
360
368
  .top-16 {
361
369
  top: calc(var(--spacing) * 16);
362
370
  }
363
- .top-full {
364
- top: 100%;
365
- }
366
371
  .right-0 {
367
372
  right: calc(var(--spacing) * 0);
368
373
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uibee",
3
- "version": "2.16.35",
3
+ "version": "2.16.37",
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": {
@@ -57,4 +57,4 @@
57
57
  "remark-gfm": "^4.0.1",
58
58
  "utilbee": "^1.4.5"
59
59
  }
60
- }
60
+ }
@@ -1,4 +1,4 @@
1
- import { type ChangeEvent, type JSX, useRef, useState } from 'react'
1
+ import { type ChangeEvent, type JSX, useRef, useState, useId } from 'react'
2
2
  import { Calendar, Clock } from 'lucide-react'
3
3
  import { FieldWrapper } from './shared'
4
4
  import DateTimePickerPopup from './shared/dateTimePickerPopup'
@@ -21,6 +21,8 @@ export default function Input(props: InputProps) {
21
21
  const { type = 'text', value } = inputProps
22
22
  const localRef = useRef<HTMLInputElement>(null)
23
23
  const [isOpen, setIsOpen] = useState(false)
24
+ const id = useId()
25
+ const anchorName = `--input-${id.replace(/:/g, '')}`
24
26
 
25
27
  const containerRef = useClickOutside<HTMLDivElement>(() => setIsOpen(false))
26
28
 
@@ -157,9 +159,8 @@ export default function Input(props: InputProps) {
157
159
  value={isDateType ? getDateDisplayValue() : value}
158
160
  readOnly={isClickableType}
159
161
  onClick={() => isClickableType && !inputProps.disabled && setIsOpen(true)}
160
- title={label}
161
- aria-invalid={!!error}
162
162
  aria-describedby={error ? `${name}-error` : undefined}
163
+ style={{ anchorName } as any}
163
164
  className={`
164
165
  w-full rounded-md bg-login-500/50 border border-login-500
165
166
  text-login-text placeholder-login-200
@@ -181,6 +182,7 @@ export default function Input(props: InputProps) {
181
182
  onChange={handleDateChange}
182
183
  type={type as 'date' | 'time' | 'datetime-local'}
183
184
  onClose={() => setIsOpen(false)}
185
+ anchorName={anchorName}
184
186
  />
185
187
  )}
186
188
  {isOpen && isColorType && !inputProps.disabled && (
@@ -188,6 +190,7 @@ export default function Input(props: InputProps) {
188
190
  value={value as string || ''}
189
191
  onChange={handleColorChange}
190
192
  onClose={() => setIsOpen(false)}
193
+ anchorName={anchorName}
191
194
  />
192
195
  )}
193
196
  </div>
@@ -4,6 +4,7 @@ export type ColorPickerPopupProps = {
4
4
  value: string
5
5
  onChange: (color: string) => void
6
6
  onClose: () => void
7
+ anchorName?: string
7
8
  }
8
9
 
9
10
  function hexToHsv(hex: string): { h: number; s: number; v: number } {
@@ -159,7 +160,7 @@ function HuePicker({ hue, onChange }: { hue: number, onChange: (h: number) => vo
159
160
  )
160
161
  }
161
162
 
162
- export default function ColorPickerPopup({ value, onChange, onClose }: ColorPickerPopupProps): JSX.Element {
163
+ export default function ColorPickerPopup({ value, onChange, onClose, anchorName }: ColorPickerPopupProps): JSX.Element {
163
164
  const [hsv, setHsv] = useState(() => hexToHsv(value || '#000000'))
164
165
  const [hexInput, setHexInput] = useState(value || '#000000')
165
166
 
@@ -191,7 +192,16 @@ export default function ColorPickerPopup({ value, onChange, onClose }: ColorPick
191
192
  }
192
193
 
193
194
  return (
194
- <div className='absolute top-full left-0 mt-1 z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-3 w-64 select-none'>
195
+ <div
196
+ className='fixed z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-3 w-64 select-none anchor-popup'
197
+ style={{
198
+ positionAnchor: anchorName,
199
+ positionArea: 'bottom span-right',
200
+ insetArea: 'bottom span-right',
201
+ positionTryFallbacks: 'flip-block',
202
+ margin: '0.25rem 0',
203
+ } as React.CSSProperties}
204
+ >
195
205
  <SaturationPicker hsv={hsv} onChange={handleSaturationChange} />
196
206
  <HuePicker hue={hsv.h} onChange={handleHueChange} />
197
207
 
@@ -6,6 +6,7 @@ type DateTimePickerPopupProps = {
6
6
  onChange: (date: Date) => void
7
7
  type: 'date' | 'time' | 'datetime-local'
8
8
  onClose?: () => void
9
+ anchorName?: string
9
10
  }
10
11
 
11
12
  const DAYS = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
@@ -19,6 +20,7 @@ export default function DateTimePickerPopup({
19
20
  onChange,
20
21
  type,
21
22
  onClose,
23
+ anchorName,
22
24
  }: DateTimePickerPopupProps) {
23
25
  const [currentDate, setCurrentDate] = useState(new Date())
24
26
  const [timeInput, setTimeInput] = useState({
@@ -218,7 +220,16 @@ export default function DateTimePickerPopup({
218
220
  }
219
221
 
220
222
  return (
221
- <div className='absolute top-full left-0 z-50 mt-1 bg-login-600 border border-login-500 rounded-md shadow-lg p-1 min-w-70'>
223
+ <div
224
+ className='fixed z-50 bg-login-600 border border-login-500 rounded-md shadow-lg p-1 min-w-70 anchor-popup'
225
+ style={{
226
+ positionAnchor: anchorName,
227
+ positionArea: 'bottom span-right',
228
+ insetArea: 'bottom span-right',
229
+ positionTryFallbacks: 'flip-block',
230
+ margin: '0.25rem 0',
231
+ } as React.CSSProperties}
232
+ >
222
233
  {type !== 'time' && renderCalendar()}
223
234
  {(type === 'time' || type === 'datetime-local') && renderTimePicker()}
224
235
  </div>
@@ -9,14 +9,22 @@ type HeaderProps = {
9
9
  variant?: 'default' | 'minimal'
10
10
  }
11
11
 
12
+ function parseOrder(value: string | null) {
13
+ return value === 'asc' || value === 'desc' ? value : undefined
14
+ }
15
+
12
16
  export default function Header({ columns, hideMenu, variant = 'default' }: HeaderProps) {
13
- const [column, setColumn] = useState(columns[0]?.key || '')
14
- const [order, setOrder] = useState<'asc' | 'desc'>('asc')
15
17
  const router = useRouter()
16
18
  const pathname = usePathname()
17
19
  const searchParams = useSearchParams()
20
+ const [column, setColumn] = useState(searchParams.get('column') ?? '')
21
+ const [order, setOrder] = useState<'asc' | 'desc' | undefined>(parseOrder(searchParams.get('order')))
18
22
 
19
23
  useEffect(() => {
24
+ if (!column || !order) {
25
+ return
26
+ }
27
+
20
28
  const params = new URLSearchParams(searchParams.toString())
21
29
  if (
22
30
  searchParams.get('order') !== order ||
package/src/globals.css CHANGED
@@ -205,6 +205,16 @@
205
205
  font-weight: 500;
206
206
  }
207
207
 
208
+ @utility anchor-popup {
209
+ top: 100%;
210
+ left: 0;
211
+
212
+ @supports (position-anchor: --foo) {
213
+ top: auto;
214
+ left: auto;
215
+ }
216
+ }
217
+
208
218
  /* Hide default browser icons/buttons for inputs */
209
219
  input::-webkit-calendar-picker-indicator {
210
220
  display: none !important;