Skip to content

Commit 04c4510

Browse files
committed
publish: version 0.9.3
1 parent c76b376 commit 04c4510

9 files changed

Lines changed: 298 additions & 56 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
66
and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.3] - 2026-03-22
9+
10+
### Added
11+
- `SortingList`
12+
13+
### Changed
14+
- `ColumnSwitcher` icon
15+
- `TableHeader` to truncate
16+
- `TableColumnSwitcher` does not show pinning icon anymore when pinning is disabled
17+
818
## [0.9.2] - 2026-03-22
919

1020
### Added

locales/de-DE.arb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"add": "Hinzufügen",
33
"addFilter": "Filter hinzufügen",
4+
"addSorting": "Sortierung hinzufügen",
45
"addTime": "Uhrzeit hinzufügen",
56
"all": "Alle",
67
"apply": "Anwenden",
@@ -319,6 +320,8 @@
319320
"decreaseSortingPriority": "Sortierpriorität verringern",
320321
"changeColumnDisplay": "Spaltenanzeige ändern",
321322
"caseSensitive": "Groß-/Klein beachten",
323+
"sortAsc": "ASC",
324+
"sortDesc": "DESC",
322325
"sorting": "Sortierung",
323326
"sSortingState": "{sortDirection, select, asc{Sortierung (Aktuell Aufsteigend)} desc{Sortierung (Aktuell Absteigend)} other{Sortierung (Aktuell Keine)}}",
324327
"@sSortingState": {

locales/en-US.arb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"add": "Add",
33
"addFilter": "Add filter",
4+
"addSorting": "Add sorting",
45
"addTime": "Add Time",
56
"all": "All",
67
"apply": "Apply",
@@ -319,6 +320,8 @@
319320
"increaseSortingPriority": "Increase sorting priority",
320321
"decreaseSortingPriority": "Decrease sorting priority",
321322
"caseSensitive": "Case Sensitive",
323+
"sortAsc": "ASC",
324+
"sortDesc": "DESC",
322325
"sorting": "Sorting",
323326
"sSortingState": "{sortDirection, select, asc{Sorting (Currently Ascending)} desc{Sorting (Currently Descending)} other{Sorting (Currently None)}}",
324327
"@sSortingState": {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"access": "public"
1111
},
1212
"license": "MPL-2.0",
13-
"version": "0.9.2",
13+
"version": "0.9.3",
1414
"files": [
1515
"dist"
1616
],

src/components/layout/table/TableColumnSwitcher.tsx

Lines changed: 42 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useMemo, useRef, useId } from 'react'
2-
import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Eye, EyeOff, Pin, PinOff, ArrowLeftRightIcon } from 'lucide-react'
2+
import { ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Eye, EyeOff, Pin, PinOff, Columns3Cog } from 'lucide-react'
33
import type { ButtonProps } from '@/src/components/user-interaction/Button'
44
import { useHightideTranslation } from '@/src/i18n/useHightideTranslation'
55
import type { PopUpProps } from '../popup/PopUp'
@@ -8,6 +8,7 @@ import { PopUpRoot } from '../popup/PopUpRoot'
88
import { PopUpOpener } from '../popup/PopUpOpener'
99
import { useTableStateWithoutSizingContext } from './TableContext'
1010
import { IconButton } from '../../user-interaction/IconButton'
11+
import { Visibility } from '../Visibility'
1112

1213
export type TableColumnSwitcherPopUpProps = PopUpProps
1314

@@ -21,6 +22,9 @@ export const TableColumnSwitcherPopUp = ({ ...props }: TableColumnSwitcherPopUpP
2122
label: `table-column-picker-label-${generatedId}`,
2223
}), [generatedId, props.id])
2324

25+
const enableHiding = table.options.enableHiding !== false
26+
const enableColumnPinning = table.options.enableColumnPinning !== false
27+
2428
const tableState = table.getState()
2529
const columnOrder = tableState.columnOrder
2630
const columnPinning = tableState.columnPinning
@@ -259,38 +263,42 @@ export const TableColumnSwitcherPopUp = ({ ...props }: TableColumnSwitcherPopUpP
259263
{getColumnHeader(columnId)}
260264
</div>
261265
<>
262-
<IconButton
263-
tooltip={translation('changeVisibility')}
264-
size="sm"
265-
color="neutral"
266-
coloringStyle="text"
267-
disabled={!column.getCanHide()}
268-
onClick={() => toggleColumnVisibility(columnId)}
269-
aria-label={isVisible ? translation('hideColumn') : translation('showColumn')}
270-
>
271-
{isVisible ? (
272-
<Eye className="size-4" />
273-
) : (
274-
<EyeOff className="size-4" />
275-
)}
276-
</IconButton>
277-
<IconButton
278-
tooltip={translation('changePinning')}
279-
size="sm"
280-
color="neutral"
281-
coloringStyle="text"
282-
disabled={!column.getCanPin()}
283-
onClick={() => {
284-
if(isPinned) {
285-
unpinColumn(columnId)
286-
} else {
287-
pinColumn(columnId, 'left')
288-
}
289-
}}
290-
aria-label={isPinned ? translation('unpin') : translation('pinLeft')}
291-
>
292-
{!isPinned ? ( <PinOff className="size-4" />) : ( <Pin className="size-4" />)}
293-
</IconButton>
266+
<Visibility isVisible={enableHiding}>
267+
<IconButton
268+
tooltip={translation('changeVisibility')}
269+
size="sm"
270+
color="neutral"
271+
coloringStyle="text"
272+
disabled={!column.getCanHide()}
273+
onClick={() => toggleColumnVisibility(columnId)}
274+
aria-label={isVisible ? translation('hideColumn') : translation('showColumn')}
275+
>
276+
{isVisible ? (
277+
<Eye className="size-4" />
278+
) : (
279+
<EyeOff className="size-4" />
280+
)}
281+
</IconButton>
282+
</Visibility>
283+
<Visibility isVisible={enableColumnPinning}>
284+
<IconButton
285+
tooltip={translation('changePinning')}
286+
size="sm"
287+
color="neutral"
288+
coloringStyle="text"
289+
disabled={!column.getCanPin()}
290+
onClick={() => {
291+
if(isPinned) {
292+
unpinColumn(columnId)
293+
} else {
294+
pinColumn(columnId, 'left')
295+
}
296+
}}
297+
aria-label={isPinned ? translation('unpin') : translation('pinLeft')}
298+
>
299+
{!isPinned ? ( <PinOff className="size-4" />) : ( <Pin className="size-4" />)}
300+
</IconButton>
301+
</Visibility>
294302
</>
295303
</div>
296304
)
@@ -317,7 +325,7 @@ export const TableColumnSwitcher = ({ buttonProps, ...props }: TableColumnSwitch
317325
tooltip={translation('changeColumnDisplay')}
318326
{...buttonProps}
319327
>
320-
<ArrowLeftRightIcon className="size-4" />
328+
<Columns3Cog className="size-5" />
321329
</IconButton>
322330
)}
323331
</PopUpOpener>

src/components/layout/table/TableHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export const TableHeader = ({ isSticky = false }: TableHeaderProps) => {
9898
className={clsx('group/table-header-cell', header.column.columnDef.meta?.className)}
9999
>
100100
<Visibility isVisible={!header.isPlaceholder}>
101-
<div className="flex-row-1 items-center">
101+
<div className="flex-row-1 items-center truncate">
102102
<Visibility isVisible={header.column.getCanSort()}>
103103
<TableSortButton
104104
sortDirection={header.column.getIsSorted()}

src/components/user-interaction/data/FilterList.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { FilterPopUp } from './FilterPopUp'
1313
import { Combobox } from '@/src/components/user-interaction/Combobox/Combobox'
1414
import { ComboboxOption } from '@/src/components/user-interaction/Combobox/ComboboxOption'
1515
import { PopUpContext } from '../../layout/popup/PopUpContext'
16-
import { ExpansionIcon } from '../../display-and-visualization/ExpansionIcon'
1716
import { FilterOperatorUtils } from './FilterOperator'
1817
import type { ColumnFilter } from '@tanstack/react-table'
1918

@@ -73,11 +72,11 @@ export const FilterList = ({ value, onValueChange, availableItems }: FilterListP
7372
}, [value, editState])
7473

7574
return (
76-
<div className="flex-row-1 flex-wrap gap-y-1">
75+
<div className="flex-row-2 flex-wrap gap-y-2">
7776
<PopUpRoot>
7877
<PopUpOpener>
7978
{({ toggleOpen, props }) => (
80-
<Button {...props} onClick={toggleOpen} color="neutral" size="md">
79+
<Button {...props} onClick={toggleOpen} color="neutral" size="sm" className="min-w-36">
8180
{translation('addFilter')}
8281
<PlusIcon className="size-4" />
8382
</Button>
@@ -135,12 +134,16 @@ export const FilterList = ({ value, onValueChange, availableItems }: FilterListP
135134
}}
136135
>
137136
<PopUpOpener>
138-
{({ toggleOpen, props, isOpen }) => (
139-
<Button {...props} onClick={toggleOpen} color="primary" coloringStyle="tonal-outline" size="md">
137+
{({ toggleOpen, props }) => (
138+
<Button {...props} onClick={toggleOpen} color="primary" coloringStyle="tonal-outline" size="sm">
140139
{item.activeLabelBuilder ?
141-
item.activeLabelBuilder(columnFilter.value) :
142-
item.label + ': ' + filterValueToLabel(columnFilter.value, { tags: item.tags })}
143-
<ExpansionIcon isExpanded={isOpen} />
140+
item.activeLabelBuilder(columnFilter.value) : (
141+
<>
142+
<span className="font-bold">{item.label}</span>
143+
{filterValueToLabel(columnFilter.value, { tags: item.tags })}
144+
</>
145+
)
146+
}
144147
</Button>
145148
)}
146149
</PopUpOpener>
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { useMemo } from 'react'
2+
import type { ColumnSort } from '@tanstack/react-table'
3+
import { DataTypeUtils, type DataType } from './data-types'
4+
import { useHightideTranslation } from '@/src/i18n/useHightideTranslation'
5+
import { ArrowDownWideNarrow, ArrowUpNarrowWide, PlusIcon, TrashIcon, XIcon } from 'lucide-react'
6+
import { PopUpRoot } from '../../layout/popup/PopUpRoot'
7+
import { PopUp } from '../../layout/popup/PopUp'
8+
import { PopUpOpener } from '../../layout/popup/PopUpOpener'
9+
import { Button } from '../Button'
10+
import { Combobox } from '@/src/components/user-interaction/Combobox/Combobox'
11+
import { ComboboxOption } from '@/src/components/user-interaction/Combobox/ComboboxOption'
12+
import { PopUpContext } from '../../layout/popup/PopUpContext'
13+
import { IconButton } from '../IconButton'
14+
import clsx from 'clsx'
15+
16+
export interface SortingListItem {
17+
id: string,
18+
label: string,
19+
dataType: DataType,
20+
}
21+
22+
export interface SortingListProps {
23+
sorting: ColumnSort[],
24+
onSortingChange: (sorting: ColumnSort[]) => void,
25+
availableItems: SortingListItem[],
26+
}
27+
28+
export const SortingList = ({ sorting, onSortingChange, availableItems }: SortingListProps) => {
29+
const translation = useHightideTranslation()
30+
const activeIds = useMemo(() => sorting.map((item) => item.id), [sorting])
31+
const inactiveItems = useMemo(
32+
() =>
33+
availableItems.filter((item) => !activeIds.includes(item.id)).sort((a, b) => a.label.localeCompare(b.label)),
34+
[availableItems, activeIds]
35+
)
36+
const itemRecord = useMemo(
37+
() =>
38+
availableItems.reduce(
39+
(acc, item) => {
40+
acc[item.id] = item
41+
return acc
42+
},
43+
{} as Record<string, SortingListItem>
44+
),
45+
[availableItems]
46+
)
47+
48+
const setSortDirection = (columnId: string, desc: boolean) => {
49+
onSortingChange(sorting.map((s) => (s.id === columnId ? { ...s, desc } : s)))
50+
}
51+
52+
const removeSort = (columnId: string) => {
53+
onSortingChange(sorting.filter((s) => s.id !== columnId))
54+
}
55+
56+
return (
57+
<div className="flex-row-2 flex-wrap gap-y-2">
58+
<PopUpRoot>
59+
<PopUpOpener>
60+
{({ toggleOpen, props }) => (
61+
<Button {...props} onClick={toggleOpen} color="neutral" size="sm" className="min-w-36">
62+
{translation('addSorting')}
63+
<PlusIcon className="size-4" />
64+
</Button>
65+
)}
66+
</PopUpOpener>
67+
<PopUp className="flex-col-2 p-2">
68+
<PopUpContext.Consumer>
69+
{({ setIsOpen }) => (
70+
<Combobox
71+
onItemClick={(id) => {
72+
const item = itemRecord[id]
73+
if (!item) return
74+
onSortingChange([...sorting, { id: item.id, desc: false }])
75+
setIsOpen(false)
76+
}}
77+
>
78+
{inactiveItems.map((item) => (
79+
<ComboboxOption key={item.id} value={item.id} label={item.label}>
80+
{DataTypeUtils.toIcon(item.dataType)}
81+
{item.label}
82+
</ComboboxOption>
83+
))}
84+
</Combobox>
85+
)}
86+
</PopUpContext.Consumer>
87+
</PopUp>
88+
</PopUpRoot>
89+
{sorting.map((columnSort) => {
90+
const item = itemRecord[columnSort.id]
91+
if (!item) return null
92+
return (
93+
<PopUpRoot key={columnSort.id}>
94+
<PopUpOpener>
95+
{({ toggleOpen, props }) => (
96+
<Button {...props} onClick={toggleOpen} color="secondary" coloringStyle="tonal-outline" size="sm">
97+
<span className="font-bold">{item.label}</span>
98+
{columnSort.desc ? <ArrowDownWideNarrow className="size-5"/> : <ArrowUpNarrowWide className="size-5"/>}
99+
</Button>
100+
)}
101+
</PopUpOpener>
102+
<PopUpContext.Consumer>
103+
{({ setIsOpen }) => (
104+
<PopUp
105+
className={clsx('flex-col-3 p-3 min-w-64')}
106+
onClose={() => setIsOpen(false)}
107+
>
108+
<div className="flex-row-4 justify-between w-full items-center gap-2">
109+
<span className="typography-title-sm font-semibold">{item.label}</span>
110+
<div className="flex-row-0 shrink-0 items-center">
111+
<IconButton
112+
tooltip={translation('removeFilter')}
113+
onClick={() => {
114+
removeSort(columnSort.id)
115+
setIsOpen(false)
116+
}}
117+
color="negative"
118+
coloringStyle="text"
119+
size="sm"
120+
>
121+
<TrashIcon className="size-4" />
122+
</IconButton>
123+
<IconButton
124+
tooltip={translation('close')}
125+
onClick={() => setIsOpen(false)}
126+
color="neutral"
127+
coloringStyle="text"
128+
size="sm"
129+
>
130+
<XIcon className="size-4" />
131+
</IconButton>
132+
</div>
133+
</div>
134+
<div className="flex-row-1 w-full gap-2">
135+
<Button
136+
type="button"
137+
className="flex-1 flex-row-1 items-center justify-center gap-2"
138+
color={columnSort.desc ? 'neutral' : 'primary'}
139+
coloringStyle="solid"
140+
size="md"
141+
onClick={() => setSortDirection(columnSort.id, false)}
142+
>
143+
<ArrowUpNarrowWide className="size-4" />
144+
{translation('sortAsc')}
145+
</Button>
146+
<Button
147+
type="button"
148+
className="flex-1 flex-row-1 items-center justify-center gap-2"
149+
color={columnSort.desc ? 'primary' : 'neutral'}
150+
coloringStyle="solid"
151+
size="md"
152+
onClick={() => setSortDirection(columnSort.id, true)}
153+
>
154+
<ArrowDownWideNarrow className="size-4" />
155+
{translation('sortDesc')}
156+
</Button>
157+
</div>
158+
</PopUp>
159+
)}
160+
</PopUpContext.Consumer>
161+
</PopUpRoot>
162+
)
163+
})}
164+
</div>
165+
)
166+
}

0 commit comments

Comments
 (0)