Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 23 additions & 11 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.14.4] - 2026-07-06

### Changed

- Updated readme.md

## [0.14.3] - 2026-07-06
## [0.14.5] - 2026-07-06

### Added

Expand All @@ -22,17 +16,35 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- `DateTimeInput` popup `data-mode` attribute for mode-specific dialog sizing
- `TabPanel` `data-in-custom-tab-view` attribute so panels skip default padding when portaled into a custom `TabView`
- `DateTimePickerDialog` Storybook story
- `TabSwitcher` `tabSwitcherWithTabView` Storybook story demonstrating a custom `TabView` layout
- `Browser Compatibility/Safari` Storybook story with demos for CSS that requires WebKit prefixes (`sticky`, `user-select`, `appearance`, `touch-action`, `transform`, `::-webkit-scrollbar`)

### Changed

- Date/time picker family (`DatePicker`, `DateTimePicker`, `DateTimePickerDialog`, `DayPicker`, `YearMonthPicker`, `DateTimeField`, `DateTimeInput`) migrated from `data-name` selectors to class-based CSS
- `FormFieldLayout` migrated from `data-name` to class-based CSS (`.form-field-container`, `.form-field-label`, `.form-field-description`, `.form-field-error`)
- `TabSwitcher` (`TabList`, `TabListItem`, `TabPanel`) migrated from `data-name` to class-based CSS
- `DatePicker` always shows today/previous/next navigation buttons (disabled outside day view); month label uses short month format
- `DateTimeInput` dialog popup uses a viewport-capped `min-width` and applies the wider tablet width only for `dateTime` mode
- `FormFieldLayout` error message moved from absolute positioning to in-flow layout with padding

### Fixed

- `TableProvider`'s `computeWidth` method not checking whether a float was parsed correctly
- Add missing enum value "time" to the translation for key `sDateTimeSelect`

## [0.14.4] - 2026-07-06

### Changed

- Updated readme.md

## [0.14.3] - 2026-07-06

### Added

- `TabSwitcher` `tabSwitcherWithTabView` Storybook story demonstrating a custom `TabView` layout
- `Browser Compatibility/Safari` Storybook story with demos for CSS that requires WebKit prefixes (`sticky`, `user-select`, `appearance`, `touch-action`, `transform`, `::-webkit-scrollbar`)

### Changed

- `TabSwitcher` (`TabList`, `TabListItem`, `TabPanel`) migrated from `data-name` to class-based CSS
- `TabPanel` default padding only applied when not rendered inside a custom `TabView`
- `TimeInput` layout no longer wraps stepper segments
- `NumberStepperInput` increment/decrement buttons use `tabIndex={-1}`
Expand Down
2 changes: 1 addition & 1 deletion locales/de-DE.arb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"theme": {}
}
},
"sDateTimeSelect": "{datetimeMode, select, date{Datum auswählen} dateTime{Datum und Uhrzeit auswählen}}",
"sDateTimeSelect": "{datetimeMode, select, time{Zeit auswählen} date{Datum auswählen} dateTime{Datum und Uhrzeit auswählen}}",
"@sDateTimeSelect": {
"placeholders": {
"datetimeMode": {}
Expand Down
2 changes: 1 addition & 1 deletion locales/en-US.arb
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
"theme": {}
}
},
"sDateTimeSelect": "{datetimeMode, select, date{Select Date} dateTime{Select Date and Time}}",
"sDateTimeSelect": "{datetimeMode, select, time{Select Time} date{Select Date} dateTime{Select Date and Time}}",
"@sDateTimeSelect": {
"placeholders": {
"datetimeMode": {}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"access": "public"
},
"license": "MPL-2.0",
"version": "0.14.4",
"version": "0.14.5",
"browserslist": [
"> 0.2%",
"last 3 versions",
Expand Down
8 changes: 4 additions & 4 deletions src/components/form/FieldLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ export const FormFieldLayout = forwardRef<HTMLDivElement, FormFieldLayoutProps>(
{...props}
ref={ref}

data-name={props['data-name'] ?? 'form-field-container'}
data-name="form-field-container"
>
{label && (
<label
{...labelProps}
id={ids.label}
htmlFor={ids.input}
data-name={labelProps?.['data-name'] ?? 'form-field-label'}
data-name="form-field-label"
>
{label}
{showRequiredIndicator && required && <div role="none" className="bg-primary w-2 h-2 rounded-full" />}
Expand All @@ -110,7 +110,7 @@ export const FormFieldLayout = forwardRef<HTMLDivElement, FormFieldLayoutProps>(
{...descriptionProps}
id={ids.description}

data-name={descriptionProps?.['data-name'] ?? 'form-field-description'}
data-name="form-field-description"
>
{description}
</p>
Expand All @@ -125,7 +125,7 @@ export const FormFieldLayout = forwardRef<HTMLDivElement, FormFieldLayoutProps>(
aria-hidden={!invalid}
aria-live="polite"

data-name={invalidDescriptionProps?.['data-name'] ?? 'form-field-error'}
data-name="form-field-error"
>
{invalidDescription}
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/components/layout/table/TableProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,19 @@ export const TableProvider = <T,>({

if(width === undefined || !el) return width
const styles = getComputedStyle(el)
width = width - parseFloat(styles.borderLeftWidth) - parseFloat(styles.borderRightWidth)
let leftBorderWidth = parseFloat(styles.borderLeftWidth)
if(isNaN(leftBorderWidth)) leftBorderWidth = 0
let rightBorderWidth = parseFloat(styles.borderRightWidth)
if(isNaN(rightBorderWidth)) rightBorderWidth = 0
width = width - leftBorderWidth - rightBorderWidth
return Math.floor(width)
}, [containerRef])

useLayoutEffect(() => {
if(!negotiatesWidths) return
setTargetWidth(computeWidth())
}, [computeWidth, negotiatesWidths])

useWindowResizeObserver(useCallback(() => {
if(!negotiatesWidths) return
setTargetWidth(computeWidth())
Expand Down
88 changes: 43 additions & 45 deletions src/components/user-interaction/date/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,57 +53,55 @@ export const DatePicker = ({
const [displayedMonth, setDisplayedMonth] = useState<Date>(new Date(value.getFullYear(), value.getMonth(), 1))
const [displayMode, setDisplayMode] = useState<DisplayMode>(initialDisplay)

const isDayMode = displayMode === 'day'

return (
<div className={clsx('flex-col-3', className)}>
<div className="flex-row-2 items-center justify-between">
<div className={clsx('date-picker', className)}>
<div className="date-picker-header">
<Button
size="sm"
color="neutral"
className={clsx('flex-row-1 items-center cursor-pointer select-none', {
'text-disabled': displayMode !== 'day',
})}
onClick={() => setDisplayMode(displayMode === 'day' ? 'yearMonth' : 'day')}
>
{`${new Intl.DateTimeFormat(LocalizationUtil.localToLanguage(locale), { month: 'long' }).format(displayedMonth)} ${displayedMonth.getFullYear()}`}
{`${new Intl.DateTimeFormat(LocalizationUtil.localToLanguage(locale), { month: 'short' }).format(displayedMonth)} ${displayedMonth.getFullYear()}`}
<ChevronDown size={16}/>
</Button>
{displayMode === 'day' && (
<div className="flex-row-2 justify-end">
<IconButton
tooltip={translation('time.today')}
size="sm"
coloringStyle="tonal"
onClick={() => {
const newDate = new Date()
newDate.setHours(value.getHours(), value.getMinutes())
setValue(newDate)
setDisplayedMonth(newDate)
}}
>
<Calendar className="size-5"/>
</IconButton>
<IconButton
tooltip={translation('time.previousMonth')}
size="sm"
disabled={!DateUtils.between(DateUtils.subtractDuration(displayedMonth, { months: 1 }), start, end)}
onClick={() => {
setDisplayedMonth(DateUtils.subtractDuration(displayedMonth, { months: 1 }))
}}
>
<ArrowUp size={20}/>
</IconButton>
<IconButton
tooltip={translation('time.nextMonth')}
size="sm"
disabled={!DateUtils.between(DateUtils.addDuration(displayedMonth, { months: 1 }), start, end)}
onClick={() => {
setDisplayedMonth(DateUtils.addDuration(displayedMonth, { months: 1 }))
}}
>
<ArrowDown size={20}/>
</IconButton>
</div>
)}
<div className="flex-row-2 justify-end">
<IconButton
tooltip={translation('time.today')}
size="sm"
coloringStyle="tonal"
disabled={!isDayMode}
onClick={() => {
const newDate = new Date()
newDate.setHours(value.getHours(), value.getMinutes())
setValue(newDate)
setDisplayedMonth(newDate)
}}
>
<Calendar className="size-5"/>
</IconButton>
<IconButton
tooltip={translation('time.previousMonth')}
size="sm"
disabled={!isDayMode || !DateUtils.between(DateUtils.subtractDuration(displayedMonth, { months: 1 }), start, end)}
onClick={() => {
setDisplayedMonth(DateUtils.subtractDuration(displayedMonth, { months: 1 }))
}}
>
<ArrowUp size={20}/>
</IconButton>
<IconButton
tooltip={translation('time.nextMonth')}
size="sm"
disabled={!isDayMode || !DateUtils.between(DateUtils.addDuration(displayedMonth, { months: 1 }), start, end)}
onClick={() => {
setDisplayedMonth(DateUtils.addDuration(displayedMonth, { months: 1 }))
}}
>
<ArrowDown size={20}/>
</IconButton>
</div>
</div>
{displayMode === 'yearMonth' ? (
<YearMonthPicker
Expand All @@ -119,7 +117,7 @@ export const DatePicker = ({
setDisplayedMonth(newDate)
setDisplayMode('day')
}}
className="h-60 max-h-60"
className="date-picker-content"
/>
) : (
<DayPicker
Expand All @@ -132,7 +130,7 @@ export const DatePicker = ({
weekStart={weekStart}
onValueChange={setValue}
onEditComplete={onEditComplete}
className="h-60 max-h-60"
className="date-picker-content"
/>
)}
</div>
Expand Down
8 changes: 5 additions & 3 deletions src/components/user-interaction/date/DateTimePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { HTMLAttributes } from 'react'
import { type ReactNode } from 'react'
import type { DateTimeFormat } from '@/src/utils/date'
import type { TimeInputProps } from './TimeInput'
Expand All @@ -6,8 +7,10 @@ import type { DatePickerProps } from './DatePicker'
import { DatePicker } from './DatePicker'
import type { FormFieldDataHandling } from '../../form/FormField'
import { useControlledState } from '@/src/hooks/useControlledState'
import clsx from 'clsx'

export interface DateTimePickerProps extends
HTMLAttributes<HTMLDivElement>,
Partial<FormFieldDataHandling<Date>>,
Pick<DatePickerProps, 'start' | 'end' | 'weekStart' | 'markToday'>,
Pick<TimeInputProps, 'is24HourFormat' | 'minuteIncrement' | 'secondIncrement' | 'millisecondIncrement' | 'precision'>
Expand Down Expand Up @@ -37,6 +40,7 @@ export const DateTimePicker = ({
onEditComplete,
timeInputProps,
datePickerProps,
...props
}: DateTimePickerProps) => {
const useDate = mode === 'dateTime' || mode === 'date'
const useTime = mode === 'dateTime' || mode === 'time'
Expand All @@ -52,8 +56,6 @@ export const DateTimePicker = ({
dateDisplay = (
<DatePicker
{...datePickerProps}
className="min-w-80"
yearMonthPickerProps={{ className: 'h-full grow' }}
start={start}
end={end}
weekStart={weekStart}
Expand All @@ -80,7 +82,7 @@ export const DateTimePicker = ({
}

return (
<div className="flex-col-2 min-h-71 max-h-71">
<div {...props} className={clsx('date-time-picker', props.className)} data-mode={mode}>
{dateDisplay}
{timeDisplay}
</div>
Expand Down
20 changes: 12 additions & 8 deletions src/components/user-interaction/date/DateTimePickerDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useControlledState } from '@/src/hooks/useControlledState'
import { useHightideTranslation } from '@/src/i18n/useHightideTranslation'
import { DateTimePicker, type DateTimePickerProps } from '@/src/components/user-interaction/date/DateTimePicker'
import type { HTMLAttributes } from 'react'
import { useEffect, useState, type ReactNode } from 'react'
import { Visibility } from '@/src/components/layout/Visibility'
import { Button } from '@/src/components/user-interaction/Button'
import type { FormFieldDataHandling } from '@/src/components/form/FormField'
import type { DateTimeFormat } from '@/src/utils/date'
import clsx from 'clsx'

export interface DateTimePickerDialogProps extends
HTMLAttributes<HTMLDivElement>,
Partial<FormFieldDataHandling<Date | null>>,
Pick<DateTimePickerProps, 'start' | 'end' | 'weekStart' | 'markToday' | 'is24HourFormat' | 'minuteIncrement' | 'secondIncrement' | 'millisecondIncrement' | 'precision'>
{
Expand Down Expand Up @@ -38,6 +41,7 @@ export const DateTimePickerDialog = ({
precision,
labelId,
label,
...props
}: DateTimePickerDialogProps) => {
const translation = useHightideTranslation()
const [state, setState] = useControlledState({
Expand All @@ -53,11 +57,11 @@ export const DateTimePickerDialog = ({
}, [state])

return (
<>
<div className="flex-row-2 justify-center w-full py-1">
<div {...props} className={clsx('date-time-picker-dialog', props?.className)}>
<div className="date-time-picker-dialog-header">
<span
id={labelId}
className="typography-title-md font-semibold"
className="date-time-picker-dialog-label"
>
{label ?? translation('sDateTimeSelect', { datetimeMode: mode })}
</span>
Expand All @@ -78,7 +82,7 @@ export const DateTimePickerDialog = ({
millisecondIncrement={millisecondIncrement}
precision={precision}
/>
<div className="flex-row-2 justify-end">
<div className="date-time-picker-dialog-actions">
<Visibility isVisible={allowRemove && !!state}>
<Button
size="md"
Expand All @@ -87,7 +91,7 @@ export const DateTimePickerDialog = ({
setState(null)
onEditComplete?.(null)
}}
className="min-w-26"
className="date-time-picker-dialog-action"
>
{translation('clear')}
</Button>
Expand All @@ -100,7 +104,7 @@ export const DateTimePickerDialog = ({
setState(state)
onEditComplete?.(state)
}}
className="min-w-26"
className="date-time-picker-dialog-action"
>
{translation('cancel')}
</Button>
Expand All @@ -110,11 +114,11 @@ export const DateTimePickerDialog = ({
onClick={() => {
onEditComplete?.(pickerState)
}}
className="min-w-26"
className="date-time-picker-dialog-action"
>
{translation('done')}
</Button>
</div>
</>
</div>
)
}
Loading
Loading