From 4aa9abf87a6ca5b31f418ddc494fb186b77ee358 Mon Sep 17 00:00:00 2001 From: DasProffi <67233923+DasProffi@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:17:28 +0100 Subject: [PATCH 01/19] feat: add HightideConfigContext and HightideProvider --- .storybook/preview.tsx | 16 +-- CHANGELOG.md | 9 ++ package.json | 2 +- src/components/date/DatePicker.tsx | 2 +- src/components/date/DayPicker.tsx | 2 +- src/components/date/YearMonthPicker.tsx | 2 +- src/components/dialog/LanguageDialog.tsx | 2 +- src/components/dialog/ThemeDialog.tsx | 4 +- src/components/table/Table.tsx | 4 +- src/components/user-action/Tooltip.tsx | 35 +++--- .../user-action/input/DateTimeInput.tsx | 2 +- src/contexts/HightideConfigContext.tsx | 109 ++++++++++++++++++ src/contexts/HightideProvider.tsx | 30 +++++ .../LocaleContext.tsx} | 25 ++-- .../ThemeContext.tsx} | 11 +- src/i18n/useHightideTranslation.ts | 2 +- src/i18n/util.ts | 6 - 17 files changed, 206 insertions(+), 57 deletions(-) create mode 100644 src/contexts/HightideConfigContext.tsx create mode 100644 src/contexts/HightideProvider.tsx rename src/{i18n/LocaleProvider.tsx => contexts/LocaleContext.tsx} (79%) rename src/{theming/useTheme.tsx => contexts/ThemeContext.tsx} (87%) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 46e6a33..84801f2 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,8 +1,7 @@ import type { Preview } from '@storybook/nextjs' import '../src/style/globals.css' import './storybookStyleOverrides.css' -import { ThemeProvider } from '../src/theming/useTheme' -import { LocaleProvider } from '../src/i18n/LocaleProvider' +import { HightideProvider } from '../src/contexts/HightideProvider' const preview: Preview = { parameters: { @@ -33,15 +32,16 @@ const preview: Preview = { (Story, context) => { const App = Story const theme = context.globals.backgrounds?.value ?? 'system' - const language = context.globals.language + const locale = context.globals.language return (
- - - - - + + +
) }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cea756..6fa5387 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ 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.6.0] - 2025-12-18 + +### Added +- `HightideConfigContext` for storing Look and Feel Variables +- `HightideProvider` to bundle all hightide contexts in one provider + +### Changed +- `Tables` to no longer round their column size + ## [0.5.4] - 2025-12-18 ### Fixed diff --git a/package.json b/package.json index f88e45b..ca99c5b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "git+https://github.com/helpwave/hightide.git" }, "license": "MPL-2.0", - "version": "0.5.4", + "version": "0.6.0", "files": [ "dist" ], diff --git a/src/components/date/DatePicker.tsx b/src/components/date/DatePicker.tsx index 8276c8a..05d799e 100644 --- a/src/components/date/DatePicker.tsx +++ b/src/components/date/DatePicker.tsx @@ -7,7 +7,7 @@ import type { DayPickerProps } from '@/src/components/date/DayPicker' import { DayPicker } from '@/src/components/date/DayPicker' import type { YearMonthPickerProps } from '@/src/components/date/YearMonthPicker' import { YearMonthPicker } from '@/src/components/date/YearMonthPicker' -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import { Button } from '@/src/components/user-action/Button' import { LocalizationUtil } from '@/src/i18n/util' diff --git a/src/components/date/DayPicker.tsx b/src/components/date/DayPicker.tsx index a400893..ab82fdf 100644 --- a/src/components/date/DayPicker.tsx +++ b/src/components/date/DayPicker.tsx @@ -1,7 +1,7 @@ import type { WeekDay } from '@/src/utils/date' import { DateUtils, getWeeksForCalenderMonth, isInTimeSpan } from '@/src/utils/date' import clsx from 'clsx' -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import { useEffect, useState } from 'react' import { Button } from '@/src/components/user-action/Button' diff --git a/src/components/date/YearMonthPicker.tsx b/src/components/date/YearMonthPicker.tsx index a7fbd98..6bd5a7f 100644 --- a/src/components/date/YearMonthPicker.tsx +++ b/src/components/date/YearMonthPicker.tsx @@ -3,7 +3,7 @@ import { equalSizeGroups, range } from '@/src/utils/array' import clsx from 'clsx' import { ExpandableUncontrolled } from '@/src/components/layout/Expandable' import { addDuration, DateUtils, subtractDuration } from '@/src/utils/date' -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import { Button } from '../user-action/Button' import { useOverwritableState } from '@/src/hooks/useOverwritableState' diff --git a/src/components/dialog/LanguageDialog.tsx b/src/components/dialog/LanguageDialog.tsx index f1f63e1..75059ff 100644 --- a/src/components/dialog/LanguageDialog.tsx +++ b/src/components/dialog/LanguageDialog.tsx @@ -2,7 +2,7 @@ import type { PropsWithChildren, ReactNode } from 'react' import type { DialogProps } from '@/src/components/dialog/Dialog' import { Dialog } from '@/src/components/dialog/Dialog' import { LocalizationUtil } from '@/src/i18n/util' -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import { Select, SelectOption } from '@/src/components/user-action/Select' import { Button } from '@/src/components/user-action/Button' import { useHightideTranslation } from '@/src/i18n/useHightideTranslation' diff --git a/src/components/dialog/ThemeDialog.tsx b/src/components/dialog/ThemeDialog.tsx index 1fbed55..0880a76 100644 --- a/src/components/dialog/ThemeDialog.tsx +++ b/src/components/dialog/ThemeDialog.tsx @@ -3,8 +3,8 @@ import type { DialogProps } from '@/src/components/dialog/Dialog' import { Dialog } from '@/src/components/dialog/Dialog' import { MonitorCog, MoonIcon, SunIcon } from 'lucide-react' import clsx from 'clsx' -import type { ThemeType } from '@/src/theming/useTheme' -import { ThemeUtil, useTheme } from '@/src/theming/useTheme' +import type { ThemeType } from '@/src/contexts/ThemeContext' +import { ThemeUtil, useTheme } from '@/src/contexts/ThemeContext' import { Select, SelectOption } from '@/src/components/user-action/Select' import { Button } from '@/src/components/user-action/Button' import { useHightideTranslation } from '@/src/i18n/useHightideTranslation' diff --git a/src/components/table/Table.tsx b/src/components/table/Table.tsx index 7c59668..403b254 100644 --- a/src/components/table/Table.tsx +++ b/src/components/table/Table.tsx @@ -305,8 +305,8 @@ export const Table = ({ const colSizes: { [key: string]: number } = {} for (let i = 0; i < headers.length; i++) { const header = headers[i]! - colSizes[`--header-${header.id}-size`] = Math.floor(header.getSize()) - colSizes[`--col-${header.column.id}-size`] = Math.floor(header.column.getSize()) + colSizes[`--header-${header.id}-size`] = header.getSize() + colSizes[`--col-${header.column.id}-size`] = header.column.getSize() } return colSizes diff --git a/src/components/user-action/Tooltip.tsx b/src/components/user-action/Tooltip.tsx index 1e16a6c..9ff4e7a 100644 --- a/src/components/user-action/Tooltip.tsx +++ b/src/components/user-action/Tooltip.tsx @@ -1,10 +1,12 @@ import type { CSSProperties, PropsWithChildren, ReactNode } from 'react' -import { useRef, useState } from 'react' +import { useMemo, useRef, useState } from 'react' import { clsx } from 'clsx' import { useZIndexRegister } from '@/src/hooks/useZIndexRegister' import { Visibility } from '@/src/components/layout/Visibility' import { useFloatingElement } from '@/src/hooks/useFloatingElement' import { createPortal } from 'react-dom' +import type { TooltipConfig } from '@/src/contexts/HightideConfigContext' +import { useHightideConfig } from '@/src/contexts/HightideConfigContext' type TooltipState = { isShown: boolean, @@ -13,20 +15,8 @@ type TooltipState = { type Position = 'top' | 'bottom' | 'left' | 'right' -export type TooltipProps = PropsWithChildren<{ +export type TooltipProps = PropsWithChildren & Partial & { tooltip: ReactNode, - /** - * Number of milliseconds until the tooltip appears - * - * defaults to 400ms - */ - appearDelay?: number, - /** - * Number of milliseconds until the tooltip disappears - * - * defaults to 50ms - */ - disappearDelay?: number, /** * Class names of additional styling properties for the tooltip */ @@ -37,7 +27,7 @@ export type TooltipProps = PropsWithChildren<{ containerClassName?: string, position?: Position, disabled?: boolean, -}> +} /** * A Component for showing a tooltip when hovering over Content @@ -52,8 +42,8 @@ export type TooltipProps = PropsWithChildren<{ export const Tooltip = ({ tooltip, children, - appearDelay = 400, - disappearDelay = 50, + appearDelay: appearDelayOverwrite, + disappearDelay: disappearDelayOverwrite, tooltipClassName = '', containerClassName = '', position = 'bottom', @@ -63,6 +53,15 @@ export const Tooltip = ({ isShown: false, timer: null, }) + const { config } = useHightideConfig() + const appearDelay = useMemo( + () => appearDelayOverwrite ?? config.tooltip.appearDelay, + [config.tooltip.appearDelay, appearDelayOverwrite] + ) + const disappearDelay = useMemo( + () => disappearDelayOverwrite ?? config.tooltip.disappearDelay, + [config.tooltip.disappearDelay, disappearDelayOverwrite] + ) const { isShown } = state const anchorRef = useRef(null) const containerRef = useRef(null) @@ -121,7 +120,7 @@ export const Tooltip = ({ ref={anchorRef} className={clsx('relative inline-block', containerClassName)} onPointerEnter={() => setState(prevState => { - if(prevState.isShown) { + if (prevState.isShown) { return prevState } return { diff --git a/src/components/user-action/input/DateTimeInput.tsx b/src/components/user-action/input/DateTimeInput.tsx index ce92131..f1da8eb 100644 --- a/src/components/user-action/input/DateTimeInput.tsx +++ b/src/components/user-action/input/DateTimeInput.tsx @@ -5,7 +5,7 @@ import clsx from 'clsx' import type { InputProps } from '@/src/components/user-action/input/Input' import { Input } from '@/src/components/user-action/input/Input' import { useOverwritableState } from '@/src/hooks/useOverwritableState' -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import { useOutsideClick } from '@/src/hooks/useOutsideClick' import { useZIndexRegister } from '@/src/hooks/useZIndexRegister' import type { DateTimePickerProps } from '@/src/components/user-action/DateAndTimePicker' diff --git a/src/contexts/HightideConfigContext.tsx b/src/contexts/HightideConfigContext.tsx new file mode 100644 index 0000000..3cac06c --- /dev/null +++ b/src/contexts/HightideConfigContext.tsx @@ -0,0 +1,109 @@ +import { createContext, type PropsWithChildren, useContext, useState } from 'react' +import type { ResolvedTheme } from '@/src/contexts/ThemeContext' +import type { HightideTranslationLocales } from '@/src/i18n/translations' +import type { DeepPartial } from '@/src/utils/typing' + +export type TooltipConfig = { + /** + * Number of milliseconds until the tooltip appears + */ + appearDelay: number, + /** + * Number of milliseconds until the tooltip disappears + */ + disappearDelay: number, +} + +export type ThemeConfig = { + /** + * The initial theme to show when: + * 1. The system preference is not or cannot be loaded + * 2. The user has not set an app preference + */ + initialTheme: ResolvedTheme, +} + +export type LocalizationConfig = { + /** + * The initial locale to use when: + * 1. The system preference is not or cannot be loaded + * 2. The user has not set an app preference + */ + defaultLocale: HightideTranslationLocales, +} + +export type HightideConfig = { + tooltip: TooltipConfig, + theme: ThemeConfig, + locale: LocalizationConfig, +} + +const defaultConfig: HightideConfig = { + tooltip: { + appearDelay: 400, + disappearDelay: 100, + }, + theme: { + initialTheme: 'light' + }, + locale: { + defaultLocale: 'de-DE', + } +} + +function mergeConfig(config: HightideConfig, overwrite: DeepPartial): HightideConfig { + return { + locale: { + ...config.locale, + ...overwrite?.locale + }, + theme: { + ...config.theme, + ...overwrite.theme, + }, + tooltip: { + ...config.tooltip, + ...overwrite.tooltip, + } + } +} + +type ConfigType = { + config: HightideConfig, + setConfig: (configOverwrite: DeepPartial) => void, +} + +export const HightideConfigContext = createContext(null) + +export type HightideConfigProviderProps = PropsWithChildren & DeepPartial + +export const HightideConfigProvider = ({ + children, + ...initialOverwrite + }: HightideConfigProviderProps) => { + const [config, setConfig] = useState(mergeConfig(defaultConfig, initialOverwrite)) + + return ( + setConfig(prevState => mergeConfig(prevState, value)), + }} + > + {children} + + ) +} + +export const useHightideConfig = () => { + const context = useContext(HightideConfigContext) + if (!context) { + return { + config: defaultConfig, + setConfig: () => { + console.error('useHightideConfig.setConfig is not available without a HightideConfigProvider. Try wrapping your app a HightideConfigProvider.') + } + } + } + return context +} \ No newline at end of file diff --git a/src/contexts/HightideProvider.tsx b/src/contexts/HightideProvider.tsx new file mode 100644 index 0000000..ad9417e --- /dev/null +++ b/src/contexts/HightideProvider.tsx @@ -0,0 +1,30 @@ +import type { PropsWithChildren } from 'react' +import type { HightideConfigProviderProps } from '@/src/contexts/HightideConfigContext' +import { HightideConfigProvider } from '@/src/contexts/HightideConfigContext' +import type { LocaleProviderProps } from '@/src/contexts/LocaleContext' +import { LocaleProvider } from '@/src/contexts/LocaleContext' +import type { ThemeProviderProps } from '@/src/contexts/ThemeContext' +import { ThemeProvider } from '@/src/contexts/ThemeContext' + +type HightideProviderProps = PropsWithChildren & { + theme?: Omit, + locale?: Omit, + config?: Omit, +} + +export const HightideProvider = ({ + children, + theme, + locale, + config, + }: HightideProviderProps) => { + return ( + + + + {children} + + + + ) +} diff --git a/src/i18n/LocaleProvider.tsx b/src/contexts/LocaleContext.tsx similarity index 79% rename from src/i18n/LocaleProvider.tsx rename to src/contexts/LocaleContext.tsx index c330fcb..6602c2c 100644 --- a/src/i18n/LocaleProvider.tsx +++ b/src/contexts/LocaleContext.tsx @@ -1,32 +1,37 @@ import type { Dispatch, PropsWithChildren, SetStateAction } from 'react' import { createContext, useContext, useEffect, useMemo, useState } from 'react' import { useLocalStorage } from '../hooks/useLocalStorage' -import { LocalizationUtil } from './util' +import { LocalizationUtil } from '../i18n/util' import type { HightideTranslationLocales } from '@/src/i18n/translations' +import type { LocalizationConfig } from '@/src/contexts/HightideConfigContext' +import { useHightideConfig } from '@/src/contexts/HightideConfigContext' export type LocaleContextValue = { locale: HightideTranslationLocales, setLocale: Dispatch>, } -export const LocaleContext = createContext({ - locale: LocalizationUtil.DEFAULT_LOCALE, - setLocale: (v) => v -}) +export const LocaleContext = createContext(null) type LocaleWithSystem = HightideTranslationLocales | 'system' -type LocaleProviderProps = { +export type LocaleProviderProps = PropsWithChildren & Partial & { locale?: LocaleWithSystem, onChangedLocale?: (locale: HightideTranslationLocales) => void, } -export const LocaleProvider = ({ children, locale, onChangedLocale }: PropsWithChildren) => { +export const LocaleProvider = ({ + children, + locale, + defaultLocale, + onChangedLocale + }: LocaleProviderProps) => { const { value: storedLocale, setValue: setStoredLocale, deleteValue: deleteStoredLocale, } = useLocalStorage('locale', 'system') + const { config } = useHightideConfig() const [localePreference, setLocalePreference] = useState('system') const resolvedLocale = useMemo(() => { @@ -39,11 +44,11 @@ export const LocaleProvider = ({ children, locale, onChangedLocale }: PropsWithC if (localePreference !== 'system') { return localePreference } - return LocalizationUtil.DEFAULT_LOCALE - }, [locale, localePreference, storedLocale]) + return config.locale.defaultLocale ?? defaultLocale + }, [config.locale.defaultLocale, defaultLocale, locale, localePreference, storedLocale]) useEffect(() => { - if(!locale) return + if (!locale) return if (locale === 'system') { deleteStoredLocale() } else { diff --git a/src/theming/useTheme.tsx b/src/contexts/ThemeContext.tsx similarity index 87% rename from src/theming/useTheme.tsx rename to src/contexts/ThemeContext.tsx index ae3d6c5..ab093c8 100644 --- a/src/theming/useTheme.tsx +++ b/src/contexts/ThemeContext.tsx @@ -1,6 +1,8 @@ import type { Dispatch, PropsWithChildren, SetStateAction } from 'react' import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { useLocalStorage } from '../hooks/useLocalStorage' +import type { ThemeConfig } from '@/src/contexts/HightideConfigContext' +import { useHightideConfig } from '@/src/contexts/HightideConfigContext' const themes = ['light', 'dark', 'system'] as const @@ -19,19 +21,20 @@ type ThemeContextType = { export const ThemeContext = createContext(null) -type ThemeProviderProps = { +export type ThemeProviderProps = PropsWithChildren & Partial & { /** * Only set this if you want to control the theme yourself */ theme?: ThemeType, } -export const ThemeProvider = ({ children, theme }: PropsWithChildren) => { +export const ThemeProvider = ({ children, theme, initialTheme }: ThemeProviderProps) => { const { value: storedTheme, setValue: setStoredTheme, deleteValue: deleteStoredTheme } = useLocalStorage('theme', 'system') + const { config } = useHightideConfig() const [themePreference, setThemePreference] = useState('system') const resolvedTheme = useMemo((): ResolvedTheme => { @@ -44,8 +47,8 @@ export const ThemeProvider = ({ children, theme }: PropsWithChildren { if(!theme) return diff --git a/src/i18n/useHightideTranslation.ts b/src/i18n/useHightideTranslation.ts index 0e96a18..23c7aea 100644 --- a/src/i18n/useHightideTranslation.ts +++ b/src/i18n/useHightideTranslation.ts @@ -1,4 +1,4 @@ -import { useLocale } from '@/src/i18n/LocaleProvider' +import { useLocale } from '@/src/contexts/LocaleContext' import type { PartialTranslationExtension, Translation, diff --git a/src/i18n/util.ts b/src/i18n/util.ts index 27a502b..e0a883a 100644 --- a/src/i18n/util.ts +++ b/src/i18n/util.ts @@ -9,11 +9,6 @@ const localsNames: Record = { 'de-DE': 'Deutsch', } -/** - * The default locale - */ -const DEFAULT_LOCALE: HightideTranslationLocales = 'en-US' - function localeToLanguage(locale: HightideTranslationLocales) { return locale.split('-')[0] } @@ -24,6 +19,5 @@ function localeToLanguage(locale: HightideTranslationLocales) { export const LocalizationUtil = { locals: hightideTranslationLocales, localToLanguage: localeToLanguage, - DEFAULT_LOCALE, languagesLocalNames: localsNames, } From e1d716a942e955b408c80130cebaca8ee9ff0011 Mon Sep 17 00:00:00 2001 From: DasProffi <67233923+DasProffi@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:03:15 +0100 Subject: [PATCH 02/19] feat: draft for Expandable.tsx splitting --- src/components/layout/Expandable.tsx | 308 +++++++++++++++++++++------ src/style/theme/components.css | 16 ++ 2 files changed, 258 insertions(+), 66 deletions(-) diff --git a/src/components/layout/Expandable.tsx b/src/components/layout/Expandable.tsx index 1e9fc2f..4819588 100644 --- a/src/components/layout/Expandable.tsx +++ b/src/components/layout/Expandable.tsx @@ -1,26 +1,234 @@ -import type { PropsWithChildren, ReactNode } from 'react' -import { forwardRef, useCallback, useId } from 'react' +import type { Dispatch, HTMLAttributes, PropsWithChildren, ReactNode, SetStateAction } from 'react' +import { useEffect } from 'react' +import { useState } from 'react' +import { createContext, forwardRef, useCallback, useContext, useId, useMemo } from 'react' import { ChevronDown } from 'lucide-react' import clsx from 'clsx' import { useOverwritableState } from '@/src/hooks/useOverwritableState' -export type ExpansionIconProps = { +// +// Context +// +type ExpandableContextIdsState = { + root: string, + header: string, + content: string, +} + +type ExpandableContextState = { + ids: ExpandableContextIdsState, + setIds: Dispatch>, + disabled: boolean, + isExpanded: boolean, + toggle: () => void, + setIsExpanded: Dispatch>, +} + +const ExpandableContext = createContext(null) + +function useExpandableContext() { + const context = useContext(ExpandableContext) + if (!context) { + throw new Error('Expandable components must be used within an ExpandableRoot') + } + return context +} + +// +// Shared Components +// + +export type ExpansionIconProps = HTMLAttributes & { isExpanded?: boolean, - className?: string, } -export const ExpansionIcon = ({ isExpanded, className }: ExpansionIconProps) => { +export const ExpansionIcon = ({ + children, + isExpanded: isExpandedOverwrite, + ...props + }: ExpansionIconProps) => { + const { isExpanded: contextIsExpanded, disabled } = useExpandableContext() + const isExpanded = useMemo(() => isExpandedOverwrite ?? contextIsExpanded, [isExpandedOverwrite, contextIsExpanded]) + + return ( +
+ {children ? ( + children + ) : ( + + )} +
+ ) +} + +// +// ExpandableRoot +// + +export type ExpandableRootProps = HTMLAttributes & { + isExpanded?: boolean, + onExpandedChange?: (isExpanded: boolean) => void, + disabled?: boolean, + allowContainerToggle?: boolean, +} + +export const ExpandableRoot = forwardRef(function ExpandableRoot({ + children, + id: providedId, + isExpanded: controlledExpanded, + onExpandedChange, + disabled = false, + allowContainerToggle = false, + ...props + }, ref) { + const generatedId = useId() + const [ids, setIds] = useState({ + root: providedId ?? `expandable-${generatedId}-root`, + header: `expandable-${generatedId}-header`, + content: `expandable-${generatedId}-content` + }) + const [isExpanded, setIsExpanded] = useOverwritableState(controlledExpanded, onExpandedChange) + + const toggle = useCallback(() => { + if (!disabled) { + setIsExpanded(!isExpanded) + } + }, [disabled, isExpanded, setIsExpanded]) + + const contextValue = useMemo(() => ({ + isExpanded: !!isExpanded, + toggle, + setIsExpanded, + ids, + setIds, + disabled + }), [isExpanded, toggle, setIsExpanded, ids, disabled]) + + return ( + +
{ + props.onClick?.(event) + if(allowContainerToggle) { + toggle() + } + }} + + data-name="expandable-root" + data-isExpanded={isExpanded ? '' : undefined} + data-disabled={disabled ? '' : undefined} + data-allowContainerToggle={allowContainerToggle ? '' : undefined} + > + {children} +
+
+ ) +}) + +// +// ExpandableHeader +// + +export type ExpandableHeaderProps = HTMLAttributes + +export const ExpandableHeader = forwardRef(function ExpandableHeader({ + children, + className, + ...props + }, ref) { + const { isExpanded, toggle, ids, setIds, disabled } = useExpandableContext() + useEffect(() => { + if(props.id) { + setIds(prevState => ({ ...prevState, header: props.id })) + } + }, [props.id, setIds]) + return ( - { + event.stopPropagation() + props.onClick?.(event) + toggle() + }} className={clsx( - 'min-w-6 w-6 min-h-6 h-6 transition-transform motion-safe:duration-200 motion-reduce:duration-0 ease-in-out', - { 'rotate-180': isExpanded }, + 'flex-row-2 py-2 px-4 rounded-lg justify-between items-center coloring-solid-hover select-none', + { + 'group-hover:brightness-97': !isExpanded, + 'hover:brightness-97': isExpanded && !disabled, + 'cursor-pointer': !disabled, + }, className )} - /> + + aria-expanded={isExpanded} + aria-controls={ids.content} + aria-disabled={disabled || undefined} + + data-isExpanded={isExpanded} + > + {children} + ) -} +}) + +// +// ExpandableContent +// + +export type ExpandableContentProps = HTMLAttributes + +export const ExpandableContent = forwardRef(function ExpandableContent({ + children, + ...props + }, ref) { + const { isExpanded, ids, setIds } = useExpandableContext() + useEffect(() => { + if(props.id) { + setIds(prevState => ({ ...prevState, content: props.id })) + } + }, [props.id, setIds]) + + return ( +
+ {children} +
+ ) +}) + + +// +// Composite / Legacy Components +// type IconBuilder = (expanded: boolean) => ReactNode @@ -30,9 +238,6 @@ export type ExpandableProps = PropsWithChildren<{ icon?: IconBuilder, isExpanded?: boolean, onChange?: (isExpanded: boolean) => void, - /** - * Whether the expansion should only happen when the header is clicked or on the entire component - */ clickOnlyOnHeader?: boolean, disabled?: boolean, className?: string, @@ -41,15 +246,12 @@ export type ExpandableProps = PropsWithChildren<{ contentExpandedClassName?: string, }> -/** - * A Component for showing and hiding content - */ export const Expandable = forwardRef(function Expandable({ children, - id: providedId, + id, label, icon, - isExpanded = false, + isExpanded, onChange, clickOnlyOnHeader = true, disabled = false, @@ -60,57 +262,32 @@ export const Expandable = forwardRef(function E }, ref) { const defaultIcon = useCallback((expanded: boolean) => , []) - icon ??= defaultIcon - - const generatedId = useId() - const id = providedId ?? generatedId + const iconBuilder = icon ?? defaultIcon return ( -
!clickOnlyOnHeader && !disabled && onChange?.(!isExpanded)} - - className={clsx( - 'flex-col-0 surface coloring-solid group rounded-lg shadow-sm', - { 'cursor-pointer': !clickOnlyOnHeader && !disabled }, className - )} + id={id} + isExpanded={isExpanded} + onExpandedChange={onChange} + disabled={disabled} + allowContainerToggle={!clickOnlyOnHeader} + className={className} > - -
+ {ctx => iconBuilder(ctx?.isExpanded ?? false)} + + + {children} -
-
+ + ) }) @@ -118,8 +295,7 @@ export const ExpandableUncontrolled = forwardRef ) -}) +}) \ No newline at end of file diff --git a/src/style/theme/components.css b/src/style/theme/components.css index ae5da00..826f718 100644 --- a/src/style/theme/components.css +++ b/src/style/theme/components.css @@ -161,6 +161,22 @@ @apply border-border text-border hover:border-primary hover:text-primary bg-transparent; } } + + *[data-name="expandable-root"]:not(.default-style-none) { + @apply flex-col-0 surface coloring-solid group rounded-lg shadow-sm; + + &:not([data-disabled])[data-allowContainerToggle] { + @apply cursor-pointer; + } + } + + *[data-name="expandable-icon"]:not(.default-style-none) { + @apply size-6 transition-transform motion-safe:duration-200 motion-reduce:duration-0 ease-in-out; + + &[data-isExpanded] { + @apply rotate-180; + } + } } /* HTML Element defaults */ From e60540f5a781f5da90e016e23abf29effdd53417 Mon Sep 17 00:00:00 2001 From: Felix Thape Date: Sat, 20 Dec 2025 01:16:13 +0100 Subject: [PATCH 03/19] Split Expandable Component --- .storybook/preview.tsx | 6 +++ CHANGELOG.md | 5 ++ package-lock.json | 12 ++--- package.json | 2 +- src/components/date/YearMonthPicker.tsx | 70 +++++++++++++------------ src/components/layout/Expandable.tsx | 61 ++++++++++----------- src/components/layout/FAQSection.tsx | 45 +++++----------- src/hooks/useOverwritableState.ts | 2 +- src/style/theme/components.css | 35 +++++++++++-- stories/layout/Expandable.stories.tsx | 27 ++++++---- stories/layout/FAQSection.stories.tsx | 68 ++++++++++++++---------- 11 files changed, 183 insertions(+), 150 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 84801f2..148e05e 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -12,6 +12,12 @@ const preview: Preview = { system: { name: 'System', value: '#FFFFFF' }, } }, + docs: { + codePanel: true, + }, + options: { + selectedPanel: 'storybook/docs/panel', + }, }, globalTypes: { language: { diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fa5387..e1b963e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,14 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - `HightideConfigContext` for storing Look and Feel Variables - `HightideProvider` to bundle all hightide contexts in one provider +- Added Code panel to storybook ### Changed - `Tables` to no longer round their column size +- Split `Expandable` into 3 components `ExpandableRoot`, `ExpandableHeader`, `ExpandableContent` + +### Fixed +- `useOverwritableState` propagating the old instead of the updated state ## [0.5.4] - 2025-12-18 diff --git a/package-lock.json b/package-lock.json index 9393fa0..fe2d0ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@helpwave/hightide", - "version": "0.5.0", + "version": "0.6.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@helpwave/hightide", - "version": "0.5.0", + "version": "0.6.0", "license": "MPL-2.0", "dependencies": { "@helpwave/internationalization": "0.4.0", @@ -42,7 +42,7 @@ "eslint": "9.31.0", "eslint-plugin-storybook": "10.1.9", "jest": "30.2.0", - "storybook": "10.1.9", + "storybook": "10.1.10", "ts-jest": "29.4.5", "tsup": "8.5.0", "typescript": "5.7.2", @@ -15872,9 +15872,9 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "10.1.9", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.9.tgz", - "integrity": "sha512-gHW/jOxLNzVw/Ys1XJovgrMFyh37ftMsLIw0l0h4fLsEyXhUABwrgjDp5bWrUmbQqemAIYVAAtw7UjPEdcHgkA==", + "version": "10.1.10", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.10.tgz", + "integrity": "sha512-oK0t0jEogiKKfv5Z1ao4Of99+xWw1TMUGuGRYDQS4kp2yyBsJQEgu7NI7OLYsCDI6gzt5p3RPtl1lqdeVLUi8A==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index ca99c5b..96b9a6d 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "eslint": "9.31.0", "eslint-plugin-storybook": "10.1.9", "jest": "30.2.0", - "storybook": "10.1.9", + "storybook": "10.1.10", "ts-jest": "29.4.5", "tsup": "8.5.0", "typescript": "5.7.2", diff --git a/src/components/date/YearMonthPicker.tsx b/src/components/date/YearMonthPicker.tsx index 6bd5a7f..ee10243 100644 --- a/src/components/date/YearMonthPicker.tsx +++ b/src/components/date/YearMonthPicker.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from 'react' import { equalSizeGroups, range } from '@/src/utils/array' import clsx from 'clsx' -import { ExpandableUncontrolled } from '@/src/components/layout/Expandable' +import { ExpandableContent, ExpandableHeader, ExpandableRoot } from '@/src/components/layout/Expandable' import { addDuration, DateUtils, subtractDuration } from '@/src/utils/date' import { useLocale } from '@/src/contexts/LocaleContext' import { Button } from '../user-action/Button' @@ -53,44 +53,46 @@ export const YearMonthPicker = ({ {years.map(year => { const selectedYear = displayedYearMonth.getFullYear() === year return ( - {year}} isExpanded={showValueOpen && selectedYear} - contentClassName="gap-y-1" - contentExpandedClassName="!p-2" > - {equalSizeGroups([...DateUtils.monthsList], 3).map((monthList, index) => ( -
- {monthList.map(month => { - const monthIndex = DateUtils.monthsList.indexOf(month) - const newDate = new Date(year, monthIndex) + + {year} + + + {equalSizeGroups([...DateUtils.monthsList], 3).map((monthList, index) => ( +
+ {monthList.map(month => { + const monthIndex = DateUtils.monthsList.indexOf(month) + const newDate = new Date(year, monthIndex) - const selectedMonth = selectedYear && monthIndex === displayedYearMonth.getMonth() - const firstOfMonth = new Date(year, monthIndex, 1) - const lastOfMonth = new Date(year, monthIndex, 1) - const isAfterStart = start === undefined || start <= addDuration(subtractDuration(lastOfMonth, { days: 1 }), { months: 1 }) - const isBeforeEnd = end === undefined || firstOfMonth <= end - const isValid = isAfterStart && isBeforeEnd - return ( - - ) - })} -
- ))} - + const selectedMonth = selectedYear && monthIndex === displayedYearMonth.getMonth() + const firstOfMonth = new Date(year, monthIndex, 1) + const lastOfMonth = new Date(year, monthIndex, 1) + const isAfterStart = start === undefined || start <= addDuration(subtractDuration(lastOfMonth, { days: 1 }), { months: 1 }) + const isBeforeEnd = end === undefined || firstOfMonth <= end + const isValid = isAfterStart && isBeforeEnd + return ( + + ) + })} +
+ ))} + + ) })} diff --git a/src/components/layout/Expandable.tsx b/src/components/layout/Expandable.tsx index 4819588..d87191b 100644 --- a/src/components/layout/Expandable.tsx +++ b/src/components/layout/Expandable.tsx @@ -5,6 +5,7 @@ import { createContext, forwardRef, useCallback, useContext, useId, useMemo } fr import { ChevronDown } from 'lucide-react' import clsx from 'clsx' import { useOverwritableState } from '@/src/hooks/useOverwritableState' +import { Visibility } from './Visibility' // // Context @@ -54,7 +55,7 @@ export const ExpansionIcon = ({
{children ? ( @@ -127,9 +128,9 @@ export const ExpandableRoot = forwardRef(fu }} data-name="expandable-root" - data-isExpanded={isExpanded ? '' : undefined} + data-expanded={isExpanded ? '' : undefined} data-disabled={disabled ? '' : undefined} - data-allowContainerToggle={allowContainerToggle ? '' : undefined} + data-containertoggleable={allowContainerToggle ? '' : undefined} > {children}
@@ -141,11 +142,14 @@ export const ExpandableRoot = forwardRef(fu // ExpandableHeader // -export type ExpandableHeaderProps = HTMLAttributes +export type ExpandableHeaderProps = HTMLAttributes & { + isUsingDefaultIcon?: boolean, +} export const ExpandableHeader = forwardRef(function ExpandableHeader({ children, className, + isUsingDefaultIcon = true, ...props }, ref) { const { isExpanded, toggle, ids, setIds, disabled } = useExpandableContext() @@ -166,23 +170,19 @@ export const ExpandableHeader = forwardRef {children} + + + ) }) @@ -206,19 +206,12 @@ export const ExpandableContent = forwardRef {children} @@ -281,12 +274,16 @@ export const Expandable = forwardRef(function E {ctx => iconBuilder(ctx?.isExpanded ?? false)} - - {children} - + + {ctx => ( + + {children} + + )} + + ) }) diff --git a/src/components/layout/FAQSection.tsx b/src/components/layout/FAQSection.tsx index 386fd09..7d794b2 100644 --- a/src/components/layout/FAQSection.tsx +++ b/src/components/layout/FAQSection.tsx @@ -1,52 +1,35 @@ import type { ReactNode } from 'react' -import clsx from 'clsx' import type { ExpandableProps } from './Expandable' -import { ExpansionIcon } from './Expandable' -import { ExpandableUncontrolled } from './Expandable' -import { MarkdownInterpreter } from './MarkdownInterpreter' - -type ContentType = { - type: 'markdown', - value: string, -} | { - type: 'custom', - value: ReactNode, -} +import { ExpandableContent, ExpandableHeader, ExpandableRoot } from './Expandable' export type FAQItem = Pick & { - id: string, title: string, - content: ContentType, + content: ReactNode, } export type FAQSectionProps = { entries: FAQItem[], - expandableClassName?: string, } -/** - * Description - */ +// TODO add a descirption export const FAQSection = ({ entries, - expandableClassName }: FAQSectionProps) => { return (
    - {entries.map(({ id, title, content, ...restProps }) => ( -
  • - ( +
  • + {title})} - clickOnlyOnHeader={false} - icon={(expanded) => ()} - className={clsx('rounded-xl', expandableClassName)} + allowContainerToggle={true} > -
    - {content.type === 'markdown' ? () : content.value} -
    - + + {title} + + + {content} + +
  • ))}
diff --git a/src/hooks/useOverwritableState.ts b/src/hooks/useOverwritableState.ts index c25c521..f099626 100644 --- a/src/hooks/useOverwritableState.ts +++ b/src/hooks/useOverwritableState.ts @@ -12,7 +12,7 @@ export const useOverwritableState = (initialValue?: T, onChange?: (value: T) const onChangeWrapper: React.Dispatch> = (action) => { const resolved = resolveSetState(action, state) setState(resolved) - onChange?.(state) + onChange?.(resolved) } return [state, onChangeWrapper] diff --git a/src/style/theme/components.css b/src/style/theme/components.css index 826f718..0afbce6 100644 --- a/src/style/theme/components.css +++ b/src/style/theme/components.css @@ -163,20 +163,45 @@ } *[data-name="expandable-root"]:not(.default-style-none) { - @apply flex-col-0 surface coloring-solid group rounded-lg shadow-sm; - - &:not([data-disabled])[data-allowContainerToggle] { + @apply flex-col-0 surface coloring-solid rounded-lg shadow-sm; + + &:not([data-disabled])[data-containertoggleable] { @apply cursor-pointer; } } *[data-name="expandable-icon"]:not(.default-style-none) { - @apply size-6 transition-transform motion-safe:duration-200 motion-reduce:duration-0 ease-in-out; + @apply flex-col-0 items-center justify-center size-6 transition-transform motion-safe:duration-200 motion-reduce:duration-0 ease-in-out; - &[data-isExpanded] { + &[data-expanded] { @apply rotate-180; } } + + *[data-name="expandable-header"]:not(.default-style-none) { + @apply flex-row-2 justify-between items-center py-2 px-4 rounded-lg select-none; + + &:not([data-disabled]) { + @apply cursor-pointer surface coloring-solid-hover; + } + + &[data-disabled] { + @apply cursor-not-allowed disabled coloring-solid; + } + } + + *[data-name="expandable-content"]:not(.default-style-none) { + @apply flex-col-2 px-4 transition-all duration-300 ease-in-out; + + + &:not([data-expanded]) { + @apply max-h-0 opacity-0 overflow-hidden py-0; + } + + &[data-expanded] { + @apply max-h-96 opacity-100 py-2 overflow-y-auto; + } + } } /* HTML Element defaults */ diff --git a/stories/layout/Expandable.stories.tsx b/stories/layout/Expandable.stories.tsx index 8900f7d..085aa6a 100644 --- a/stories/layout/Expandable.stories.tsx +++ b/stories/layout/Expandable.stories.tsx @@ -1,12 +1,12 @@ import type { Meta, StoryObj } from '@storybook/nextjs' import { action } from 'storybook/actions' -import { ExpandableUncontrolled } from '../../src/components/layout/Expandable' +import { ExpandableContent, ExpandableRoot, ExpandableHeader } from '../../src/components/layout/Expandable' import { range } from '../../src/utils/array' const meta = { title: 'Layout', - component: ExpandableUncontrolled, -} satisfies Meta + component: ExpandableRoot, +} satisfies Meta export default meta type Story = StoryObj; @@ -15,14 +15,19 @@ export const expandable: Story = { args: { isExpanded: false, disabled: false, - clickOnlyOnHeader: true, - label: (Label), - contentExpandedClassName: 'overflow-y-hidden', - children: ( -
- {range(20).map(value => (
{`Item ${value}`}
))} -
- ), + allowContainerToggle: true, onChange: action('onChange'), }, + render: (args) => ( + + + {'Label'} + + + {range(5).map((value) => ( +
{`Item ${value}`}
+ ))} +
+
+ ), } diff --git a/stories/layout/FAQSection.stories.tsx b/stories/layout/FAQSection.stories.tsx index cd0ff5e..b93422d 100644 --- a/stories/layout/FAQSection.stories.tsx +++ b/stories/layout/FAQSection.stories.tsx @@ -1,47 +1,57 @@ -import type { StoryFn } from '@storybook/nextjs' +import type { Meta, StoryObj } from '@storybook/nextjs' import { FAQSection } from '../../src/components/layout/FAQSection' import { HelpwaveLogo } from '../../src/components/icons-and-geometry/HelpwaveLogo' +import { action } from 'storybook/actions' +import { MarkdownInterpreter } from '../../src/components/layout/MarkdownInterpreter' const meta = { title: 'Layout', component: FAQSection, -} +} satisfies Meta export default meta +type Story = StoryObj; -export const FAQSectionExample: StoryFn = () => ( - + ) + }, { - id: 'question2', title: 'What is the first paragraph of the lorem ipsum?', - content: { - type: 'markdown', - value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce eget suscipit ex. In vitae leo metus.' + - ' Fusce gravida urna et magna consectetur mollis. Lorem ipsum dolor sit amet, consectetur adipiscing elit.' + - ' Etiam ac tellus purus. Integer vel sollicitudin leo. Integer nec interdum nisl. Nunc bibendum tellus vel' + - ' mollis cursus. Mauris eu luctus ipsum. Vivamus euismod nisi at odio tristique volutpat. Cras sed' + - ' facilisis neque, ac sagittis turpis. Maecenas et libero facilisis dui porta suscipit et in quam.' + - ' In hac habitasse platea dictumst. Donec nec sodales nibh, a pellentesque purus.' - } + content: ( + + + ) }, { - id: 'question3', title: 'Can I click this?', - content: { - type: 'markdown', - value: '\\positive{\\b{Yes, you can.}}' - } + content: ( + + ) }, { - id: 'question4', title: 'What does the helpwave logo look like?', - content: { - type: 'custom', - value: (
) - } + content: ( +
+ ) }, - ]} - /> -) + ] + }, +} From 845343e187983cfc1b945907616984345aa5f753 Mon Sep 17 00:00:00 2001 From: Felix Thape Date: Sat, 20 Dec 2025 20:44:36 +0100 Subject: [PATCH 04/19] separte component level css into different files to allow for more granular imports --- eslint.config.js | 10 +- src/components/date/DatePicker.tsx | 8 +- src/components/date/DayPicker.tsx | 2 +- src/components/date/TimePicker.tsx | 8 +- src/components/date/YearMonthPicker.tsx | 2 +- src/components/dialog/Dialog.tsx | 2 +- .../loading-states/LoadingButton.tsx | 2 +- src/components/table/TableFilterButton.tsx | 6 +- src/components/table/TableSortButton.tsx | 2 +- src/components/user-action/Button.tsx | 72 +--- src/components/user-action/Select.tsx | 4 +- .../user-action/input/DateTimeInput.tsx | 6 +- .../user-action/input/SearchBar.tsx | 2 +- src/style/theme/colors/component.css | 18 +- src/style/theme/colors/semantic.css | 25 +- src/style/theme/colors/utilities.css | 4 + src/style/theme/components.css | 331 ------------------ src/style/theme/components/button.css | 85 +++++ src/style/theme/components/card.css | 13 + src/style/theme/components/chip.css | 13 + src/style/theme/components/general.css | 28 ++ src/style/theme/components/index.css | 8 + src/style/theme/components/input-elements.css | 108 ++++++ src/style/theme/components/link.css | 5 + src/style/theme/components/scrollbar.css | 55 +++ src/style/theme/components/table.css | 29 ++ src/style/theme/index.css | 10 +- src/style/utitlity/coloring.css | 12 +- src/style/utitlity/focus.css | 23 ++ src/style/utitlity/index.css | 3 +- stories/user-action/button/Button.stories.tsx | 32 +- 31 files changed, 475 insertions(+), 453 deletions(-) delete mode 100644 src/style/theme/components.css create mode 100644 src/style/theme/components/button.css create mode 100644 src/style/theme/components/card.css create mode 100644 src/style/theme/components/chip.css create mode 100644 src/style/theme/components/general.css create mode 100644 src/style/theme/components/index.css create mode 100644 src/style/theme/components/input-elements.css create mode 100644 src/style/theme/components/link.css create mode 100644 src/style/theme/components/scrollbar.css create mode 100644 src/style/theme/components/table.css create mode 100644 src/style/utitlity/focus.css diff --git a/eslint.config.js b/eslint.config.js index 1ae5b2b..d5f4e50 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,8 +3,8 @@ import config from '@helpwave/eslint-config' export default [ { - ignores: ['dist/**'], -}, + ignores: ['dist/**'], + }, ...config.recommended, ...storybook.configs['flat/recommended'], { @@ -15,5 +15,11 @@ export default [ }, { ignores: ['src/i18n/translations.ts'], + }, + { + // TODO add this to helpwave eslint config + rules: { + indent: ['warn', 2] + } } ] \ No newline at end of file diff --git a/src/components/date/DatePicker.tsx b/src/components/date/DatePicker.tsx index 05d799e..2a64195 100644 --- a/src/components/date/DatePicker.tsx +++ b/src/components/date/DatePicker.tsx @@ -49,7 +49,7 @@ export const DatePicker = ({
)} - - ) -} +import { MinusIcon } from 'lucide-react' const meta = { title: 'User Action/Button', - component: ExampleButton, + component: Button, argTypes: { color: { control: 'select', options: ButtonUtil.colors, }, - startIcon: StorybookHelper.iconSelect, - endIcon: StorybookHelper.iconSelect, }, -} satisfies Meta +} satisfies Meta export default meta type Story = StoryObj; @@ -36,11 +21,18 @@ type Story = StoryObj; export const button: Story = { args: { children: 'Test', + disabled: false, color: 'primary', - size: 'medium', + size: 'md', coloringStyle: 'solid', layout: 'default', - disabled: false, onClick: action('Clicked'), }, + render: ({ layout, children, ...props }) => { + return ( + + ) + } } From f9e862820bb6e9d2add8c294a75dc61fe5670059 Mon Sep 17 00:00:00 2001 From: Felix Thape Date: Wed, 24 Dec 2025 01:11:07 +0100 Subject: [PATCH 05/19] chore: restructure folders and move component styling to css --- .gitignore | 2 + CHANGELOG.md | 117 +++++++- src/components/branding/HelpwaveBadge.tsx | 8 +- .../HelpwaveLogo.tsx | 24 +- src/components/dialog/InputDialog.tsx | 26 -- .../display-and-visualization/Avatar.tsx | 128 +++++++++ .../display-and-visualization/Chip.tsx | 70 +++++ .../Circle.tsx | 10 +- .../ExpansionIcon.tsx | 33 +++ .../Label.tsx | 10 +- .../ProgressIndicator.tsx | 16 +- .../Ring.tsx | 60 ++-- .../Tag.tsx | 6 +- src/components/form/FormElementWrapper.tsx | 34 +-- src/components/icons-and-geometry/Avatar.tsx | 155 ----------- src/components/layout/Carousel.tsx | 52 ++-- src/components/layout/Chip.tsx | 87 ------ src/components/layout/DividerInserter.tsx | 10 +- src/components/layout/Expandable.tsx | 110 +++----- src/components/layout/FAQSection.tsx | 4 +- src/components/layout/FloatingContainer.tsx | 22 +- src/components/layout/ListBox.tsx | 104 +++---- src/components/layout/MarkdownInterpreter.tsx | 136 ++++----- src/components/layout/TabSwitcher.tsx | 257 ++++++++++++++++++ src/components/layout/TabView.tsx | 144 ---------- src/components/layout/TextImage.tsx | 18 +- src/components/layout/VerticalDivider.tsx | 14 +- .../{ => layout}/dialog/ConfirmDialog.tsx | 22 +- src/components/{ => layout}/dialog/Dialog.tsx | 26 +- .../dialog/DiscardChangesDialog.tsx | 20 +- src/components/layout/dialog/InputDialog.tsx | 26 ++ .../{ => layout}/dialog/LanguageDialog.tsx | 20 +- .../{ => layout}/dialog/ThemeDialog.tsx | 20 +- .../loading}/ErrorComponent.tsx | 6 +- .../loading}/LoadingAndErrorComponent.tsx | 18 +- .../loading}/LoadingAnimation.tsx | 8 +- .../loading}/LoadingContainer.tsx | 0 .../layout/navigation/BreadCrumbs.tsx | 65 +++++ .../{ => layout}/navigation/Navigation.tsx | 32 +-- .../{ => layout}/navigation/Pagination.tsx | 16 +- .../{ => layout}/navigation/StepperBar.tsx | 36 +-- src/components/layout/table/FillerCell.tsx | 15 + src/components/{ => layout}/table/Filter.ts | 0 src/components/{ => layout}/table/Table.tsx | 224 +++++++-------- .../{ => layout}/table/TableCell.tsx | 6 +- .../{ => layout}/table/TableFilterButton.tsx | 12 +- .../{ => layout}/table/TableSortButton.tsx | 22 +- .../loading-states/LoadingButton.tsx | 24 -- src/components/navigation/BreadCrumb.tsx | 48 ---- src/components/table/FillerRowElement.tsx | 14 - src/components/user-action/Checkbox.tsx | 106 -------- .../Button.tsx | 24 +- src/components/user-interaction/Checkbox.tsx | 94 +++++++ .../CopyToClipboardWrapper.tsx | 0 .../Menu.tsx | 28 +- .../ScrollPicker.tsx | 12 +- .../Select.tsx | 249 +++++++++-------- .../Textarea.tsx | 48 ++-- .../Tooltip.tsx | 45 +-- .../date}/DateAndTimePicker.tsx | 52 +++- .../date/DatePicker.tsx | 56 ++-- .../{ => user-interaction}/date/DayPicker.tsx | 38 +-- .../date/TimeDisplay.tsx | 6 +- .../date/TimePicker.tsx | 60 ++-- .../date/YearMonthPicker.tsx | 32 +-- .../input/DateTimeInput.tsx | 34 +-- .../input/Input.tsx | 31 ++- .../input/InsideLabelInput.tsx | 18 +- .../input/SearchBar.tsx | 14 +- .../input/ToggleableInput.tsx | 22 +- .../properties/CheckboxProperty.tsx | 39 +-- .../properties/DateProperty.tsx | 16 +- .../properties/MultiSelectProperty.tsx | 26 +- .../properties/NumberProperty.tsx | 18 +- .../properties/PropertyBase.tsx | 20 +- .../properties/SelectProperty.tsx | 20 +- .../properties/TextProperty.tsx | 16 +- src/components/utils/FocusTrap.tsx | 12 +- src/components/utils/Transition.tsx | 8 +- src/contexts/HightideConfigContext.tsx | 20 +- src/hooks/focus/useFocusTrap.ts | 10 +- src/hooks/useFloatingElement.ts | 37 ++- src/hooks/useOverwritableState.ts | 8 +- src/style/theme/colors/component.css | 12 +- src/style/theme/colors/utilities.css | 28 +- src/style/theme/components/avatar.css | 74 +++++ src/style/theme/components/breadcrumb.css | 15 + src/style/theme/components/button.css | 138 ++++------ src/style/theme/components/checkbox.css | 49 ++++ src/style/theme/components/chip.css | 25 +- src/style/theme/components/expandable.css | 33 +++ src/style/theme/components/expansion-icon.css | 13 + src/style/theme/components/index.css | 8 + src/style/theme/components/input-elements.css | 72 +---- src/style/theme/components/table.css | 75 +++-- src/style/theme/components/tabswitcher.css | 23 ++ src/style/theme/components/tooltip.css | 7 + src/style/utitlity/shadow.css | 69 +++-- src/utils/bagFunctions.ts | 26 +- src/utils/dataAttribute.ts | 12 + src/utils/propsExtender.ts | 64 +++++ .../HelpwaveBadge.stories.tsx | 3 +- .../HelpwaveLoadingAnimation.stories.tsx | 3 +- .../Avatar/Avatar.stories.tsx | 42 +++ .../Avatar/AvatarGroup.stories.tsx | 52 ++++ .../Chip}/Chip.stories.tsx | 8 +- .../Chip}/ChipList.stories.tsx | 12 +- .../Geometry}/Circle.stories.tsx | 3 +- .../Geometry/Ring}/AnimatedRing.stories.tsx | 3 +- .../Geometry/Ring}/RadialRings.stories.tsx | 3 +- .../Geometry/Ring}/Ring.stories.tsx | 3 +- .../Geometry/Ring}/RingWave.stories.tsx | 3 +- .../ProgressIndicator.stories.tsx | 3 +- .../LanguageDialog.stories.tsx | 15 + .../Translation.stories.tsx | 58 ++++ stories/Layout/Carousel.stories.tsx | 66 +++++ .../Layout/Dialog/ConfirmDialog.stories.tsx | 23 ++ .../Dialog/DiscardChangesDialog.stories.tsx | 19 ++ .../Layout/Dialog/StackedDialog.stories.tsx | 66 +++++ .../{layout => Layout}/Expandable.stories.tsx | 1 - .../{layout => Layout}/FAQSection.stories.tsx | 6 +- stories/Layout/FloatingContainer.stories.tsx | 73 +++++ .../LoadingAndErrorContainer.stories.tsx | 3 +- .../Loading}/LoadingContainer.stories.tsx | 3 +- .../MarkdownInterpreter.stories.tsx | 1 - .../Layout/Navigation/BreadCrumbs.stories.tsx | 20 ++ .../Navigation/Navigation.stories.tsx} | 3 +- stories/Layout/TabSwitcher.stories.tsx | 39 +++ stories/{layout => Layout}/Table.stories.tsx | 117 +++++++- .../VerticalDivider.stories.tsx | 1 - stories/Themeing/ThemeDialog.stories.tsx | 15 + .../All.stories.tsx} | 1 - .../Button.stories.tsx | 5 +- .../Checkbox.stories.tsx | 6 +- .../CopyToClipboardWrapper.stories.tsx | 21 ++ .../DatePicker.stories.tsx | 5 +- .../DateTimePicker.stories.tsx | 26 ++ .../DayPicker.stories.tsx | 7 +- .../TimePicker.stories.tsx | 6 +- .../YearMonthPicker.stories.tsx | 7 +- .../Form}/Form.stories.tsx | 25 +- .../Input}/DateTimeInput.stories.tsx | 5 +- .../Input}/Input.stories.tsx | 3 +- .../Input}/InsideLabelInput.stories.tsx | 3 +- .../Input}/SearchBar.stories.tsx | 3 +- .../Input}/TogglableInput.stories.tsx | 5 +- .../Label.stories.tsx | 3 +- .../ListBox.stories.tsx | 6 +- stories/User Interaction/Menu.stories.tsx | 44 +++ .../Properties/CheckboxProperty.stories.tsx | 35 +++ .../Properties/DateProperty.stories.tsx | 47 ++++ .../MultiSelectProperty.stories.tsx | 61 +++++ .../Properties/NumberProperty.stories.tsx | 38 +++ .../Properties}/PropertyBase.stories.tsx | 3 +- .../SingleSelectProperty.stories.tsx | 60 ++++ .../Properties/TextProperty.stories.tsx | 36 +++ .../ScrollPicker.stories.tsx | 3 +- .../Select}/MultiSelect.stories.tsx | 7 +- .../MultiSelectChipDisplay.stories.tsx | 7 +- .../Select}/Select.stories.tsx | 7 +- .../StepperBar.stories.tsx | 3 +- .../Textarea.stories.tsx | 3 +- .../Tooltip/Tooltip.stories.tsx | 34 +++ .../Tooltip/TooltipMany.stories.tsx | 37 +++ .../Tooltip/TooltipStack.stories.tsx | 39 +++ .../Focus Management/FocusTrap.stories.tsx | 88 ++++++ stories/Utilities/hooks/useDelay.stories.tsx | 37 +++ stories/branding/Avatar.stories.tsx | 41 --- stories/branding/AvatarGroup.stories.tsx | 55 ---- stories/layout/Carousel.stories.tsx | 67 ----- stories/layout/FloatingContainer.stories.tsx | 66 ----- stories/layout/TabView.stories.tsx | 37 --- .../layout/dialog/ConfirmDialog.stories.tsx | 67 ----- .../dialog/DiscardChangesDialog.stories.tsx | 61 ----- .../layout/dialog/LanguageDialog.stories.tsx | 37 --- .../layout/dialog/StackedDialog.stories.tsx | 69 ----- stories/layout/dialog/ThemeDialog.stories.tsx | 36 --- stories/navigation/BreadCrumbs.stories.tsx | 21 -- stories/other/Translation.stories.tsx | 49 ---- .../CopyToClipBoardWrapper.stories.tsx | 30 -- stories/user-action/Menu.stories.tsx | 52 ---- .../button/LoadingButton.stories.tsx | 27 -- .../date/DateTimePicker.stories.tsx | 78 ------ .../properties/CheckboxProperty.stories.tsx | 48 ---- .../properties/DateProperty.stories.tsx | 63 ----- .../MultiSelectProperty.stories.tsx | 73 ----- .../properties/NumberProperty.stories.tsx | 49 ---- .../SingleSelectProperty.stories.tsx | 71 ----- .../properties/TextProperty.stories.tsx | 49 ---- .../user-action/tooltip/Tooltip.stories.tsx | 34 --- .../tooltip/TooltipMany.stories.tsx | 39 --- .../tooltip/TooltipStack.stories.tsx | 38 --- .../focus-management/FocusTrap.stories.tsx | 88 ------ stories/util/hooks/useDelay.stories.tsx | 41 --- 194 files changed, 3680 insertions(+), 3398 deletions(-) rename src/components/{icons-and-geometry => branding}/HelpwaveLogo.tsx (63%) delete mode 100644 src/components/dialog/InputDialog.tsx create mode 100644 src/components/display-and-visualization/Avatar.tsx create mode 100644 src/components/display-and-visualization/Chip.tsx rename src/components/{icons-and-geometry => display-and-visualization}/Circle.tsx (69%) create mode 100644 src/components/display-and-visualization/ExpansionIcon.tsx rename src/components/{user-action => display-and-visualization}/Label.tsx (75%) rename src/components/{loading-states => display-and-visualization}/ProgressIndicator.tsx (71%) rename src/components/{icons-and-geometry => display-and-visualization}/Ring.tsx (82%) rename src/components/{icons-and-geometry => display-and-visualization}/Tag.tsx (83%) delete mode 100644 src/components/icons-and-geometry/Avatar.tsx delete mode 100644 src/components/layout/Chip.tsx create mode 100644 src/components/layout/TabSwitcher.tsx delete mode 100644 src/components/layout/TabView.tsx rename src/components/{ => layout}/dialog/ConfirmDialog.tsx (80%) rename src/components/{ => layout}/dialog/Dialog.tsx (88%) rename src/components/{ => layout}/dialog/DiscardChangesDialog.tsx (63%) create mode 100644 src/components/layout/dialog/InputDialog.tsx rename src/components/{ => layout}/dialog/LanguageDialog.tsx (71%) rename src/components/{ => layout}/dialog/ThemeDialog.tsx (80%) rename src/components/{loading-states => layout/loading}/ErrorComponent.tsx (80%) rename src/components/{loading-states => layout/loading}/LoadingAndErrorComponent.tsx (69%) rename src/components/{loading-states => layout/loading}/LoadingAnimation.tsx (70%) rename src/components/{loading-states => layout/loading}/LoadingContainer.tsx (100%) create mode 100644 src/components/layout/navigation/BreadCrumbs.tsx rename src/components/{ => layout}/navigation/Navigation.tsx (87%) rename src/components/{ => layout}/navigation/Pagination.tsx (89%) rename src/components/{ => layout}/navigation/StepperBar.tsx (75%) create mode 100644 src/components/layout/table/FillerCell.tsx rename src/components/{ => layout}/table/Filter.ts (100%) rename src/components/{ => layout}/table/Table.tsx (75%) rename src/components/{ => layout}/table/TableCell.tsx (70%) rename src/components/{ => layout}/table/TableFilterButton.tsx (93%) rename src/components/{ => layout}/table/TableSortButton.tsx (76%) delete mode 100644 src/components/loading-states/LoadingButton.tsx delete mode 100644 src/components/navigation/BreadCrumb.tsx delete mode 100644 src/components/table/FillerRowElement.tsx delete mode 100644 src/components/user-action/Checkbox.tsx rename src/components/{user-action => user-interaction}/Button.tsx (56%) create mode 100644 src/components/user-interaction/Checkbox.tsx rename src/components/{user-action => user-interaction}/CopyToClipboardWrapper.tsx (100%) rename src/components/{user-action => user-interaction}/Menu.tsx (84%) rename src/components/{user-action => user-interaction}/ScrollPicker.tsx (96%) rename src/components/{user-action => user-interaction}/Select.tsx (73%) rename src/components/{user-action => user-interaction}/Textarea.tsx (61%) rename src/components/{user-action => user-interaction}/Tooltip.tsx (87%) rename src/components/{user-action => user-interaction/date}/DateAndTimePicker.tsx (53%) rename src/components/{ => user-interaction}/date/DatePicker.tsx (72%) rename src/components/{ => user-interaction}/date/DayPicker.tsx (77%) rename src/components/{ => user-interaction}/date/TimeDisplay.tsx (92%) rename src/components/{ => user-interaction}/date/TimePicker.tsx (75%) rename src/components/{ => user-interaction}/date/YearMonthPicker.tsx (80%) rename src/components/{user-action => user-interaction}/input/DateTimeInput.tsx (83%) rename src/components/{user-action => user-interaction}/input/Input.tsx (74%) rename src/components/{user-action => user-interaction}/input/InsideLabelInput.tsx (71%) rename src/components/{user-action => user-interaction}/input/SearchBar.tsx (80%) rename src/components/{user-action => user-interaction}/input/ToggleableInput.tsx (71%) rename src/components/{ => user-interaction}/properties/CheckboxProperty.tsx (51%) rename src/components/{ => user-interaction}/properties/DateProperty.tsx (79%) rename src/components/{ => user-interaction}/properties/MultiSelectProperty.tsx (60%) rename src/components/{ => user-interaction}/properties/NumberProperty.tsx (83%) rename src/components/{ => user-interaction}/properties/PropertyBase.tsx (83%) rename src/components/{ => user-interaction}/properties/SelectProperty.tsx (66%) rename src/components/{ => user-interaction}/properties/TextProperty.tsx (80%) create mode 100644 src/style/theme/components/avatar.css create mode 100644 src/style/theme/components/breadcrumb.css create mode 100644 src/style/theme/components/checkbox.css create mode 100644 src/style/theme/components/expandable.css create mode 100644 src/style/theme/components/expansion-icon.css create mode 100644 src/style/theme/components/tabswitcher.css create mode 100644 src/style/theme/components/tooltip.css create mode 100644 src/utils/dataAttribute.ts create mode 100644 src/utils/propsExtender.ts rename stories/{branding => Branding}/HelpwaveBadge.stories.tsx (75%) rename stories/{branding => Branding}/HelpwaveLoadingAnimation.stories.tsx (75%) create mode 100644 stories/Display And Visualization/Avatar/Avatar.stories.tsx create mode 100644 stories/Display And Visualization/Avatar/AvatarGroup.stories.tsx rename stories/{layout/chip => Display And Visualization/Chip}/Chip.stories.tsx (70%) rename stories/{layout/chip => Display And Visualization/Chip}/ChipList.stories.tsx (56%) rename stories/{geometry => Display And Visualization/Geometry}/Circle.stories.tsx (72%) rename stories/{geometry/rings => Display And Visualization/Geometry/Ring}/AnimatedRing.stories.tsx (76%) rename stories/{geometry/rings => Display And Visualization/Geometry/Ring}/RadialRings.stories.tsx (77%) rename stories/{geometry/rings => Display And Visualization/Geometry/Ring}/Ring.stories.tsx (74%) rename stories/{geometry/rings => Display And Visualization/Geometry/Ring}/RingWave.stories.tsx (78%) rename stories/{other => Display And Visualization}/ProgressIndicator.stories.tsx (73%) create mode 100644 stories/Internationalization/LanguageDialog.stories.tsx create mode 100644 stories/Internationalization/Translation.stories.tsx create mode 100644 stories/Layout/Carousel.stories.tsx create mode 100644 stories/Layout/Dialog/ConfirmDialog.stories.tsx create mode 100644 stories/Layout/Dialog/DiscardChangesDialog.stories.tsx create mode 100644 stories/Layout/Dialog/StackedDialog.stories.tsx rename stories/{layout => Layout}/Expandable.stories.tsx (97%) rename stories/{layout => Layout}/FAQSection.stories.tsx (90%) create mode 100644 stories/Layout/FloatingContainer.stories.tsx rename stories/{other => Layout/Loading}/LoadingAndErrorContainer.stories.tsx (74%) rename stories/{other => Layout/Loading}/LoadingContainer.stories.tsx (73%) rename stories/{layout => Layout}/MarkdownInterpreter.stories.tsx (97%) create mode 100644 stories/Layout/Navigation/BreadCrumbs.stories.tsx rename stories/{navigation/NavigationMenu.stories.tsx => Layout/Navigation/Navigation.stories.tsx} (86%) create mode 100644 stories/Layout/TabSwitcher.stories.tsx rename stories/{layout => Layout}/Table.stories.tsx (64%) rename stories/{layout => Layout}/VerticalDivider.stories.tsx (95%) create mode 100644 stories/Themeing/ThemeDialog.stories.tsx rename stories/{typography/all.stories.tsx => Typography/All.stories.tsx} (98%) rename stories/{user-action/button => User Interaction}/Button.stories.tsx (81%) rename stories/{user-action => User Interaction}/Checkbox.stories.tsx (87%) create mode 100644 stories/User Interaction/CopyToClipboardWrapper.stories.tsx rename stories/{user-action/date => User Interaction/Date and Time Input}/DatePicker.stories.tsx (89%) create mode 100644 stories/User Interaction/Date and Time Input/DateTimePicker.stories.tsx rename stories/{user-action/date => User Interaction/Date and Time Input}/DayPicker.stories.tsx (70%) rename stories/{user-action/date => User Interaction/Date and Time Input}/TimePicker.stories.tsx (78%) rename stories/{user-action/date => User Interaction/Date and Time Input}/YearMonthPicker.stories.tsx (84%) rename stories/{user-action/form => User Interaction/Form}/Form.stories.tsx (89%) rename stories/{user-action/input => User Interaction/Input}/DateTimeInput.stories.tsx (89%) rename stories/{user-action/input => User Interaction/Input}/Input.stories.tsx (93%) rename stories/{user-action/input => User Interaction/Input}/InsideLabelInput.stories.tsx (90%) rename stories/{user-action/input => User Interaction/Input}/SearchBar.stories.tsx (81%) rename stories/{user-action/input => User Interaction/Input}/TogglableInput.stories.tsx (81%) rename stories/{user-action => User Interaction}/Label.stories.tsx (76%) rename stories/{user-action => User Interaction}/ListBox.stories.tsx (92%) create mode 100644 stories/User Interaction/Menu.stories.tsx create mode 100644 stories/User Interaction/Properties/CheckboxProperty.stories.tsx create mode 100644 stories/User Interaction/Properties/DateProperty.stories.tsx create mode 100644 stories/User Interaction/Properties/MultiSelectProperty.stories.tsx create mode 100644 stories/User Interaction/Properties/NumberProperty.stories.tsx rename stories/{user-action/properties => User Interaction/Properties}/PropertyBase.stories.tsx (83%) create mode 100644 stories/User Interaction/Properties/SingleSelectProperty.stories.tsx create mode 100644 stories/User Interaction/Properties/TextProperty.stories.tsx rename stories/{user-action => User Interaction}/ScrollPicker.stories.tsx (80%) rename stories/{user-action/select => User Interaction/Select}/MultiSelect.stories.tsx (87%) rename stories/{user-action/select => User Interaction/Select}/MultiSelectChipDisplay.stories.tsx (86%) rename stories/{user-action/select => User Interaction/Select}/Select.stories.tsx (89%) rename stories/{user-action => User Interaction}/StepperBar.stories.tsx (81%) rename stories/{user-action => User Interaction}/Textarea.stories.tsx (90%) create mode 100644 stories/User Interaction/Tooltip/Tooltip.stories.tsx create mode 100644 stories/User Interaction/Tooltip/TooltipMany.stories.tsx create mode 100644 stories/User Interaction/Tooltip/TooltipStack.stories.tsx create mode 100644 stories/Utilities/Focus Management/FocusTrap.stories.tsx create mode 100644 stories/Utilities/hooks/useDelay.stories.tsx delete mode 100644 stories/branding/Avatar.stories.tsx delete mode 100644 stories/branding/AvatarGroup.stories.tsx delete mode 100644 stories/layout/Carousel.stories.tsx delete mode 100644 stories/layout/FloatingContainer.stories.tsx delete mode 100644 stories/layout/TabView.stories.tsx delete mode 100644 stories/layout/dialog/ConfirmDialog.stories.tsx delete mode 100644 stories/layout/dialog/DiscardChangesDialog.stories.tsx delete mode 100644 stories/layout/dialog/LanguageDialog.stories.tsx delete mode 100644 stories/layout/dialog/StackedDialog.stories.tsx delete mode 100644 stories/layout/dialog/ThemeDialog.stories.tsx delete mode 100644 stories/navigation/BreadCrumbs.stories.tsx delete mode 100644 stories/other/Translation.stories.tsx delete mode 100644 stories/user-action/CopyToClipBoardWrapper.stories.tsx delete mode 100644 stories/user-action/Menu.stories.tsx delete mode 100644 stories/user-action/button/LoadingButton.stories.tsx delete mode 100644 stories/user-action/date/DateTimePicker.stories.tsx delete mode 100644 stories/user-action/properties/CheckboxProperty.stories.tsx delete mode 100644 stories/user-action/properties/DateProperty.stories.tsx delete mode 100644 stories/user-action/properties/MultiSelectProperty.stories.tsx delete mode 100644 stories/user-action/properties/NumberProperty.stories.tsx delete mode 100644 stories/user-action/properties/SingleSelectProperty.stories.tsx delete mode 100644 stories/user-action/properties/TextProperty.stories.tsx delete mode 100644 stories/user-action/tooltip/Tooltip.stories.tsx delete mode 100644 stories/user-action/tooltip/TooltipMany.stories.tsx delete mode 100644 stories/user-action/tooltip/TooltipStack.stories.tsx delete mode 100644 stories/util/focus-management/FocusTrap.stories.tsx delete mode 100644 stories/util/hooks/useDelay.stories.tsx diff --git a/.gitignore b/.gitignore index 4fda35e..57c544d 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,11 @@ next-env.d.ts # Storybook storybook-static +debug-storybook.log # build dist/ +.next # generated translations src/i18n/translations.ts \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e1b963e..7159875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,25 +8,35 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.6.0] - 2025-12-18 ### Added + - `HightideConfigContext` for storing Look and Feel Variables - `HightideProvider` to bundle all hightide contexts in one provider - Added Code panel to storybook ### Changed + - `Tables` to no longer round their column size - Split `Expandable` into 3 components `ExpandableRoot`, `ExpandableHeader`, `ExpandableContent` +- storybook folder structure and removed title from stories to allow for dynamic folder structure +- Component styling to be CSS and data-attribute based instead of relying on in component classNames +- Moved `ExpansionIcon` out of `Expandable` file and into its own +- Changed internal folder structure ### Fixed + - `useOverwritableState` propagating the old instead of the updated state +- `Table` background to overflow on the edges ## [0.5.4] - 2025-12-18 ### Fixed + - `useFloatingElement` calculating when no container exists yet ## [0.5.3] - 2025-12-18 ### Fixed + - `DateTimeInput` missing translation - a bug where changing days caused the minutes to change as well - the many `Tooltip`s story @@ -34,19 +44,23 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.5.2] - 2025-12-18 ### Fixed + - `LoadingAndErrorComponent` and `Visibility` to always return a `JSX.Element` ## [0.5.1] - 2025-12-18 ### Fixed + - fixed race condition in tooltip ## [0.5.0] - 2025-12-17 ### Added + - VisibilityComponent ### Changed + - Upgrade to Storybook 10.0.0 - Disable `Button`s `onClick` event propagation by default - This can be reactivated with the `allowClickEventPropagation` flag @@ -56,22 +70,27 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Changed Tooltip to be position based on anchor an not relative ### Fixed -- tooltips not disappearing if mouseleave happens too fast + +- tooltips not disappearing if mouseleave happens too fast - .arb variable typing not set ### Removed -- Tests for translation parser which are now in [@helpwave/hightide](https://github.com/helpwave/hightide) + +- Tests for translation parser which are now in [@helpwave/hightide](https://github.com/helpwave/hightide) ### Security + - Update dependencies ## [0.4.0] - 2025-12-16 ### Added + - A [conventions document](/documentation/conventions.md) - A `input-element` style for all user-input elements ### Changed + - All user-input elements now provide `data-disabled`, `data-invalid` and `data-value` attributes for styling - Focus styling now uses these tailwind utilities - `focus-style-outline` provided also as `focus-style-default` for every element (deactivatable with `focus-style-none`) @@ -83,20 +102,24 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.3.0] - 2025-12-15 ### Added + - `coloring-` CSS class uses the colors below to color the element according to the color - `prmiary`, `secondary`, `positive`, `negative`, `warning`, `disabled` CSS classes to set the variables for `coloring-*` - `useZIndexRegistry` hook to manage zIndex of elements that pop in and out of the viewport like `Select`, `Dialog`, `Tooltip` ### Changed + - Consolidated `SolidButton`, `TextButton`, `OutlinedButton`, `IconButton` into on component `Button` with four attributes `ButtonSize`, `ButtonColoringStyle`, `ButtonLayout`, `ButtonColor` -- Split css classes into theming and utility +- Split css classes into theming and utility - `SelectOption`s now only use marker padding when there is a selected value - Changed styling of `DayPicker` to make the currently selected day more easily visible ### Fixed + - Fixed `Carousel` having negative indexes for when left button is used ### Removed + - `SolidButton` - `TextButton` - `OutlinedButton` @@ -108,101 +131,123 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.2.0] - 2025-12-15 ### Added + - `PromiseUtils` with a `sleep` and `delayed` function ### Changed + - `CheckBox` now propagates value changes with `onCheckedChange` ### Removed -- Removed `row` and `col` css utilities use `flex-row-2` and `flex-col-2` instead - - Regex for checking usage ``("|'|`| )col("|'|`| )`` or ``("|'|`| )row("|'|`| )`` + +- Removed `row` and `col` css utilities use `flex-row-2` and `flex-col-2` instead + - Regex for checking usage `` ("|'|`| )col("|'|`| ) `` or `` ("|'|`| )row("|'|`| ) `` - Removed dependency on `radix-ui` ### Fixed + - Allow Tables to be sorted by multiple columns - Pagination max Page count now has the same size as the Input for the current page ### Security + - Update and pin all dependencies ## [0.1.48] - 2025-12-01 ### Added + - Added `TabView` and `Tab` for easily changing between tabs ### Update + - `"@helpwave/internationalization": "0.4.0"` ## [0.1.47] - 2025-11-24 ### Change + - `tsup` now only uses one entrypoint - Merged `build` and `build-production` into one `build` script ### Fix + - fix commonJS and module exports ## [0.1.46] - 2025-11-24 ### Fix + - Fix build to properly recognize external packages ### Security + - Remove unused dependencies ## [0.1.45] - 2025-11-24 ### Upgrade + - Increase version of `@helpwave/internationalization` to `0.3.0` ## [0.1.44] - 2025-11-23 ### Changed + - Change the name of the translations to `HightideTranslation` to be easier to integrate with other translations ## [0.1.43] - 2025-11-21 ### Changed + - Changed translation to use the arb format and ICU string replacements - Moved translations to the [locales folder](locales/) - Locales are now used instead of Languages - translations are now split into 2 translation functions - `useTranslation`: directly usable and uses the preferred locale - - `useICUTranslation`: parses every input with a ICU interpreter (less efficient) + - `useICUTranslation`: parses every input with a ICU interpreter (less efficient) ### Fixed + - fixed padding on the `InsideLabel` to be properly aligned ## [0.1.42] - 2025-10-31 ### Fixed + - Fixed `NavigationItemWithSubItem` to make all links have the same length int the menu - Fixed `
  • ` elements in `Navigation` ## [0.1.41] - 2025-10-31 ### Fixed + - Fixed `ThemeDialog` and `LanguageDialog` to properly show select options ## [0.1.40] - 2025-10-30 ### Fixed + - Fixed `Carousel` having more than one focusable item ## [0.1.39] - 2025-10-30 ### Changed + - Changed `Carousel` to have an event `onSlideChanged` when the slide changes ### Removed + - Removed `TextImage` component ## [0.1.38] - 2025-10-30 ### Changed + - Changed `Dialog`s to only be part of the DOM-Tree when open ### Fixed + - Fixed `SelectButton` not reacting correctly to arrow keys when determining the highlighted value - Fixed `ThemeProvider` and `LanguageProvider` to consider the `system` value as an overwrite - Fixed `ConfirmDialog` story using a wrong initial state @@ -210,101 +255,123 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.1.37] - 2025-10-30 ### Changed + - Exported and renamed `NavigationItem` to `NavigationItemType` ### Fixed + - Fixed `Dialog` not being client side by default ## [0.1.36] - 2025-10-06 ### Changed -- Changed `useLocalStorage` to remove values that produce an error on load + +- Changed `useLocalStorage` to remove values that produce an error on load ### Fixed + - Fixed closing animation for `Dialog` - Fixed `LanguageProvider` and `ThemeProvider` to not set undefined values into storage ## [0.1.35] - 2025-10-06 ### Added + - Added `--clean` option to barrel script - Added `useOverwritableState` to wrap `useState` and `useEffect` in uncontrolled components - Added `FormElementWrapper` onTouched callback and aria-attributes - Added `ValidatorError`s and translation for selection items ### Changed + - Changed `barrel` script location to a dedicated [scripts folder](scripts) - Split `build` script in `build` and `build-production` - Stopped saving of system theme and language values and instead load them if value is not found ### Removed + - Removed index.ts files from version control to force direct imports - Removed usages of `noop` ## [0.1.34] - 2025-10-03 ### Changed + - Changed Dialog z-index to 100 ### Fixed + - Fix `FormElementWrapper` labelledBy misspelling ## [0.1.33] - 2025-10-03 ### Changed + - Change `Dialog` to only use fixed positions ## [0.1.32] - 2025-10-03 ### Changed + - Changed the default background for surfaces and inputs to create higher contrasts ### Fixed + - Fix a `Dialog`s description rendering a div when not set - Fix initial misalignment of `Dialog`s in some cases ## [0.1.31] - 2025-10-03 ### Changed + - Make `SingleSelectProperty` and `MultiSelectProperty` use `SelectOption`s for styling ## [0.1.30] - 2025-10-03 ### Changed + - Changed `SingleSelectProperty` and `MultiSelectProperty` to accept a label ## [0.1.29] - 2025-10-02 ### Added + - HTML elements now use `color-scheme: dark` when in dark mode - Add invalid state styling to Selects - Add a placeholder color called `placeholder` - Add a hook for localized validation translation `useTranslatedValidators` ### Changed + - `disabled` and `required` are now optional in `FormElementWrapper` - changed focus to draw an outline instead of a ring ### Removed + - removed several typography entries that only change the `font-weight` (e.g. `typography-label-md-bold` -> `typography-label-md font-bold`) ### Fix + - Fix disabled color for `Select` ## [0.1.28] - 2025-10-02 ### Added + - added barreling script and barrel files ### Changed + - pin dependencies ### Fixed + - Fix css export path ## [0.1.27] - 2025-10-02 ### Added + - Add a theme preference listener to `useTheme` hook - Add icons to the Theme dialog - Add a config attribute to the `SelectRoot` component @@ -312,12 +379,14 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add accessibility for carousel ### Changed + - move `isMultiSelect` attribute of `SelectRoot` into the config `SelectConfiguration` - split `layout-and-navigation` into `layout` and `navigation` (same for stories) ## [0.1.26] - 2025-09-24 ### Added + - Add `FloatingContainer` and `useFloatingElement` for aligning a floating element - Add `ListBox` for selecting a value from a list - Added accessibility for `Select`, `MultiSelect`, `Expandable`, `Avatar` @@ -328,6 +397,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Add `useIsMounted` for checking when a component is rendered ### Changed + - Renamed `textstyle` to `typography` for ClassNames - Changed css folder to style folder - Changed `HelpwaveBadge`, `HelpwaveLogo` to allow for different sizes @@ -338,6 +408,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Changed relative to absolute imports (only partial) ### Removed + - removed typographies (full list below) - `typography-title-3xl` - `typography-title-2xl` @@ -361,20 +432,24 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.1.25] - 2025-07-19 ### Added + - Added a story for a form example ### Fixed + - Fixed `ThemeProvider` to correctly use the stored theme in the context - Fixed disabled state and styling on `Checkbox`, `Input`, `Select`, `MultiSelect` ## [0.1.24] - 2025-07-17 ### Fixed -- Fixed `useTheme` and `ThemeProvider` to correctly load the theme + +- Fixed `useTheme` and `ThemeProvider` to correctly load the theme ## [0.1.23] - 2025-07-17 ### Changed + - Changed `Avatar` to show backup icon when no name or image supplied - Changed component to use next `Image` and `Link` - Changed CSS to use referential values @@ -385,44 +460,54 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.1.22] - 2025-07-16 ### Added + - Added `coloredHoverBackground` option to `TextButton` - Added colors for input elements to css instead of using surface ### Changed + - Change `Avatar` component to allow for name displays - Change color for `Modal`s and `Menu`s ### Fixed + - Fixed `Menu` and `Overlay` z-Indexes ## [0.1.21] - 2025-07-14 ### Added + - Save theme choice to local storage ### Changed + - Changed storybook background - Changed color on dark surface to be brighter ### Fixed + - Fixed `Menu` and `Overlay` z-Indexes ## [0.1.20] - 2025-07-14 ### Added + - Added `LoadingContainer` for showing a loading animation ### Changed + - Changed `Expandable` to allow for styling the expansion container ## [0.1.19] - 2025-07-11 ### Added + - Add animations for `Expandable`, `Select`, `MultiSelect`, `Menu`, `Modal`, `Dialog` -- Add utility for defining a flex-column or flex-row with its gap directly +- Add utility for defining a flex-column or flex-row with its gap directly - Add hook `usePopoverPosition` to easily define the position of a popover based on a trigger element ### Changed + - Changed the disabled color for dark and light mode - Changed overlay background color to be configurable - Changed `Select` and `MultiSelect` to now be variants of `Menu` @@ -433,34 +518,40 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [0.1.18] - 2025-07-07 ### Fix + - fix console logging in `useDelay` - fix helpwave icon animation for safari ## [0.1.17] - 2025-07-07 ### Fix + - fix `TableWithSelection` access to `bodyRowClassName` ## [0.1.16] - 2025-07-07 ### Added + - Added a `useDelay` story - Added a `CopyToClipboardWrapper` to allow for easy copy to clipboard buttons ### Changed -- `TableWithSelection` now allows for disabling row selection +- `TableWithSelection` now allows for disabling row selection ## [0.1.13] - 2025-07-04 ### Added + - Added a `TableCell` component which is used to display all unspecified table cells ### Changed + - `TableState` is now optional on the table ### Fixed -- Fixed `Table` stories to only use known properties + +- Fixed `Table` stories to only use known properties ## [0.1.12] - 2025-07-02 @@ -472,7 +563,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Added a page input to the pagination component - Added a `resolveSetState` function to easily get the value that result from a `SetState` in a `Dispatch` - Added `useRerender` hook to allow for easy rerendering -- Added `useFocusMangement`, `useFocusOnceVisible`. `useResizeCallbackWrapper` +- Added `useFocusMangement`, `useFocusOnceVisible`. `useResizeCallbackWrapper` ### Changed @@ -484,7 +575,6 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Fixed `CheckBox` disabled state - ## [0.1.11] - 2025-07-02 ### Changed @@ -613,6 +703,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Update the `Tooltip` appearance ### Removed + - examples which are now part of their corresponding story ## [Unreleased] diff --git a/src/components/branding/HelpwaveBadge.tsx b/src/components/branding/HelpwaveBadge.tsx index e61e976..b7a2d2f 100644 --- a/src/components/branding/HelpwaveBadge.tsx +++ b/src/components/branding/HelpwaveBadge.tsx @@ -1,4 +1,4 @@ -import { HelpwaveLogo } from '../icons-and-geometry/HelpwaveLogo' +import { HelpwaveLogo } from './HelpwaveLogo' import type { HTMLAttributes } from 'react' import clsx from 'clsx' @@ -12,9 +12,9 @@ export type HelpwaveBadgeProps = HTMLAttributes & { * A Badge with the helpwave logo and the helpwave name */ export const HelpwaveBadge = ({ - size = 'sm', - ...props - }: HelpwaveBadgeProps) => { + size = 'sm', + ...props +}: HelpwaveBadgeProps) => { return ( & { * The helpwave loading spinner based on the svg logo. */ export const HelpwaveLogo = ({ - color = 'currentColor', - size, - animate = 'none', - ...props - }: HelpwaveProps) => { + color = 'currentColor', + size, + animate = 'none', + ...props +}: HelpwaveProps) => { const isLoadingAnimation = animate === 'loading' let svgAnimationKey = '' @@ -40,16 +40,16 @@ export const HelpwaveLogo = ({ > + d="M146 644.214C146 498.088 253.381 379.629 385.843 379.629" stroke={color} strokeDasharray="1000"/> + d="M625.686 645.272C493.224 645.272 385.843 526.813 385.843 380.687" stroke={color} + strokeDasharray="1000"/> + d="M533.585 613.522C533.585 508.895 610.47 424.079 705.312 424.079" stroke={color} + strokeDasharray="1000"/> + d="M878 615.639C782.628 615.639 705.313 530.822 705.313 426.196" stroke={color} + strokeDasharray="1000"/> ) diff --git a/src/components/dialog/InputDialog.tsx b/src/components/dialog/InputDialog.tsx deleted file mode 100644 index 7592893..0000000 --- a/src/components/dialog/InputDialog.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import type { InputProps } from '../user-action/input/Input' -import { Input } from '../user-action/input/Input' -import type { ConfirmDialogProps } from '@/src/components/dialog/ConfirmDialog' -import { ConfirmDialog } from '@/src/components/dialog/ConfirmDialog' - -export type InputModalProps = ConfirmDialogProps & { - inputs: InputProps[], -} - -/** - * A modal for receiving multiple inputs - */ -export const InputDialog = ({ - inputs, - buttonOverwrites, - ...props - }: InputModalProps) => { - return ( - - {inputs.map((inputProps, index) => )} - - ) -} diff --git a/src/components/display-and-visualization/Avatar.tsx b/src/components/display-and-visualization/Avatar.tsx new file mode 100644 index 0000000..6336d34 --- /dev/null +++ b/src/components/display-and-visualization/Avatar.tsx @@ -0,0 +1,128 @@ +import type { HTMLAttributes } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { UserIcon } from 'lucide-react' +import { Visibility } from '../layout/Visibility' + +export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | null + +type ImageConfig = { + avatarUrl: string, + alt: string, +} + + +export type AvatarProps = Omit, 'children'> & { + image?: ImageConfig, + name?: string, + size?: AvatarSize, +} + +/** + * A component for showing a profile picture + */ +export const Avatar = ({ + image: initialImage, + name, + size = 'md', + ...props +}: AvatarProps) => { + const [hasError, setHasError] = useState(false) + const [hasLoaded, setHasLoaded] = useState(false) + const [image, setImage] = useState(initialImage) + + const displayName = useMemo(() => { + const maxLetters = size === 'sm' ? 1 : 2 + return (name ?? '') + .split(' ') + .filter((_, index) => index < maxLetters) + .map(value => value[0]) + .join('') + .toUpperCase() + }, [name, size]) + + const isShowingImage = !!image && (!hasError || !hasLoaded) + const dataName = props['data-name'] ?? 'avatar' + + useEffect(() => { + if(initialImage?.avatarUrl !== image?.avatarUrl) { + setHasError(false) + setHasLoaded(false) + } + setImage(initialImage) + }, [image?.avatarUrl, initialImage]) + + return ( +
    + + {image?.alt} setHasLoaded(true)} + onError={() => setHasError(true)} + + data-name={`${dataName}-image`} + data-error={hasError ? '' : undefined} + data-loaded={hasLoaded ? '' : undefined} + /> + + {name ? displayName : ()} +
    + ) +} + +export type AvatarGroupProps = HTMLAttributes & { + avatars: Omit[], + showTotalNumber?: boolean, + size?: AvatarSize, +} + +/** + * A component for showing a group of Avatar's + */ +export const AvatarGroup = ({ + avatars, + showTotalNumber = true, + size = 'md', + ...props +}: AvatarGroupProps) => { + const maxShownProfiles = 5 + const displayedProfiles = avatars.length < maxShownProfiles ? avatars : avatars.slice(0, maxShownProfiles) + const notDisplayedProfiles = avatars.length - maxShownProfiles + const group = ( +
    + {displayedProfiles.map((avatar, index) => ( + + ))} +
    + ) + + + return ( +
    + {group} + {showTotalNumber && notDisplayedProfiles > 0 && ( + + {`+ ${notDisplayedProfiles}`} + + )} +
    + ) +} \ No newline at end of file diff --git a/src/components/display-and-visualization/Chip.tsx b/src/components/display-and-visualization/Chip.tsx new file mode 100644 index 0000000..6e2b4e6 --- /dev/null +++ b/src/components/display-and-visualization/Chip.tsx @@ -0,0 +1,70 @@ +import type { HTMLAttributes } from 'react' +import { ButtonUtil } from '@/src/components/user-interaction/Button' + +type ChipSize = 'xs' | 'sm' | 'md' | 'lg' | null + +type ChipColoringStyle = 'solid' | 'tonal' | null + +const chipColors = ButtonUtil.colors +export type ChipColor = typeof chipColors[number] + +export const ChipUtil = { + colors: chipColors, +} + +export type ChipProps = HTMLAttributes & { + color?: ChipColor, + coloringStyle?: ChipColoringStyle, + size?: ChipSize, +} + +/** + * A component for displaying a single chip + */ +export const Chip = ({ + children, + color = 'neutral', + coloringStyle = 'solid', + size = 'md', + ...props +}: ChipProps) => { + return ( +
    + {children} +
    + ) +} + +export type ChipListProps = HTMLAttributes & { + list: ChipProps[], +} + +/** + * A component for displaying a list of chips + */ +export const ChipList = ({ + list, + ...props +}: ChipListProps) => { + return ( +
      + {list.map((value, index) => ( +
    • + + {value.children} + +
    • + ))} +
    + ) +} diff --git a/src/components/icons-and-geometry/Circle.tsx b/src/components/display-and-visualization/Circle.tsx similarity index 69% rename from src/components/icons-and-geometry/Circle.tsx rename to src/components/display-and-visualization/Circle.tsx index 95c7f84..2a811a4 100644 --- a/src/components/icons-and-geometry/Circle.tsx +++ b/src/components/display-and-visualization/Circle.tsx @@ -7,11 +7,11 @@ export type CircleProps = Omit, 'children' | 'col } export const Circle = ({ - radius = 20, - className = 'bg-primary', - style, - ...restProps - }: CircleProps) => { + radius = 20, + className = 'bg-primary', + style, + ...restProps +}: CircleProps) => { const size = radius * 2 return (
    & { + isExpanded: boolean, + disabled?: boolean, +} + +export const ExpansionIcon = ({ + children, + isExpanded, + disabled = false, + ...props +}: ExpansionIconProps) => { + + return ( +
    + {children ? ( + children + ) : ( + + )} +
    + ) +} \ No newline at end of file diff --git a/src/components/user-action/Label.tsx b/src/components/display-and-visualization/Label.tsx similarity index 75% rename from src/components/user-action/Label.tsx rename to src/components/display-and-visualization/Label.tsx index cab1c13..93129fb 100644 --- a/src/components/user-action/Label.tsx +++ b/src/components/display-and-visualization/Label.tsx @@ -17,11 +17,11 @@ export type LabelProps = LabelHTMLAttributes & { * A Label component */ export const Label = ({ - children, - size = 'md', - className, - ...props - }: LabelProps) => { + children, + size = 'md', + className, + ...props +}: LabelProps) => { return (