Skip to content

Commit 6533622

Browse files
authored
Merge pull request #80 from kleros/fix/bug-fixes-and-updates
Fix/bug fixes and updates
2 parents 957f17c + f1234cc commit 6533622

19 files changed

+550
-102
lines changed

README.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,20 @@ npm install @kleros/ui-components-library
5555

5656
### Setup
5757

58-
1. Import the CSS at the top level of your application:
58+
1. Import the CSS:
5959

60-
```javascript
61-
import "@kleros/ui-components-library/style.css";
62-
```
60+
a. For Non-tailwind apps, import the CSS at top level of your app.
61+
62+
```javascript
63+
import "@kleros/ui-components-library/style.css";
64+
```
65+
66+
b. For Tailwind apps, import the theme and mark the library as a source in your global.css file.
67+
68+
```css
69+
@import "../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
70+
@source "../../../node_modules/@kleros/ui-components-library";
71+
```
6372

6473
2. Import and use components in your application:
6574

@@ -82,8 +91,8 @@ function MyComponent() {
8291
If you wish the use the library's tailwind theme variables in your tailwind app. You can utilize it by importing the theme file in your `global.css` file.
8392

8493
```css
85-
@import tailwindcss @import
86-
"../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
94+
@import tailwindcss;
95+
@import "../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
8796
```
8897

8998
You can find the available theme variables [here](src/styles/theme.css).

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kleros/ui-components-library",
3-
"version": "3.3.4",
3+
"version": "3.4.5",
44
"description": "UI components library which implements the Kleros design system.",
55
"source": "./src/lib/index.ts",
66
"main": "./dist/index.js",
@@ -52,6 +52,7 @@
5252
"@storybook/test": "^8.6.4",
5353
"@tailwindcss/postcss": "^4.0.11",
5454
"@tailwindcss/vite": "^4.1.4",
55+
"@types/lodash": "^4",
5556
"@types/node": "^22.13.10",
5657
"@types/react": "^18.0.9",
5758
"@types/react-dom": "^18.0.3",
@@ -93,6 +94,7 @@
9394
"@internationalized/date": "^3.7.0",
9495
"bignumber.js": "^9.1.2",
9596
"clsx": "^2.1.1",
97+
"lodash": "^4.17.21",
9698
"react": "^18.0.0",
9799
"react-aria-components": "^1.7.1",
98100
"react-dom": "^18.0.0",

src/lib/accordion/accordion-item.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,24 @@ const AccordionItem: React.FC<AccordionItemProps> = ({
2525
return (
2626
<div className="my-2">
2727
<Button
28+
id="expand-button"
2829
className={cn(
2930
"bg-klerosUIComponentsWhiteBackground border-klerosUIComponentsStroke border",
3031
"hover-medium-blue hover-short-transition hover:cursor-pointer",
31-
"rounded-[3px] px-8 py-[11.5px]",
32+
"rounded-[3px] px-4 py-[11.5px] md:px-8",
3233
"flex w-full items-center justify-between",
3334
)}
3435
onPress={() => setExpanded(expanded ? -1 : index)}
3536
>
3637
{title}
3738
{expanded ? (
38-
<Minus className={cn("fill-klerosUIComponentsPrimaryText size-4")} />
39+
<Minus
40+
className={cn("fill-klerosUIComponentsPrimaryText size-4 shrink-0")}
41+
/>
3942
) : (
40-
<Plus className={cn("fill-klerosUIComponentsPrimaryText size-4")} />
43+
<Plus
44+
className={cn("fill-klerosUIComponentsPrimaryText size-4 shrink-0")}
45+
/>
4146
)}
4247
</Button>
4348
<div
@@ -47,7 +52,7 @@ const AccordionItem: React.FC<AccordionItemProps> = ({
4752
"transition-[height] duration-(--klerosUIComponentsTransitionSpeed) ease-initial",
4853
)}
4954
>
50-
<div className="p-8" ref={ref}>
55+
<div className="p-4 md:p-8" id="body-wrapper" ref={ref}>
5156
{body}
5257
</div>
5358
</div>

src/lib/container/modal.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ interface ModalProps
1414
/** classname that applies to the modal overlay. */
1515
modalOverlayClassname?: ModalOverlayProps["className"];
1616
children?: DialogProps["children"];
17+
ariaLabel?: string;
1718
}
1819

1920
/** A modal is an overlay element which blocks interaction with elements outside it. */
2021
function Modal({
2122
className,
2223
modalOverlayClassname,
2324
children,
25+
ariaLabel,
2426
...props
2527
}: Readonly<ModalProps>) {
2628
return (
@@ -36,6 +38,7 @@ function Modal({
3638
>
3739
<AriaModal {...props}>
3840
<Dialog
41+
aria-label={ariaLabel ?? "Modal"}
3942
className={cn(
4043
"bg-klerosUIComponentsWhiteBackground h-[200px] w-[328px] outline-none",
4144
"rounded-base box-border",

src/lib/draggable-list/index.tsx

Lines changed: 63 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import React, { useEffect } from "react";
2-
import { useListData } from "react-stately";
1+
import React from "react";
32
import {
43
Button,
54
ListBox,
@@ -13,12 +12,8 @@ import { cn } from "../../utils";
1312
import DragAndDropIcon from "../../assets/svgs/drag-and-drop.svg";
1413
import Trash from "../../assets/svgs/trash.svg";
1514
import clsx from "clsx";
15+
import { ListItem, useList } from "./useList";
1616

17-
type ListItem = {
18-
id: string | number;
19-
name: string;
20-
value: any;
21-
};
2217
interface IDraggableList
2318
extends Omit<
2419
ListBoxProps<ListBoxItemProps>,
@@ -53,24 +48,26 @@ function DraggableList({
5348
deletionDisabled = false,
5449
...props
5550
}: Readonly<IDraggableList>) {
56-
const list = useListData({
51+
const {
52+
items: list,
53+
moveAfter,
54+
moveBefore,
55+
remove,
56+
getItem,
57+
} = useList({
5758
initialItems: items,
59+
onChange: updateCallback,
5860
});
5961

60-
useEffect(() => {
61-
if (!updateCallback) return;
62-
updateCallback(list.items);
63-
}, [list, updateCallback, items]);
64-
6562
const { dragAndDropHooks } = useDragAndDrop({
6663
getItems: (keys) =>
67-
[...keys].map((key) => ({ "text/plain": list.getItem(key)!.name })),
64+
[...keys].map((key) => ({ "text/plain": getItem(key)!.name })),
6865
getAllowedDropOperations: () => ["move"],
6966
onReorder(e) {
7067
if (e.target.dropPosition === "before") {
71-
list.moveBefore(e.target.key, e.keys);
68+
moveBefore(e.target.key, e.keys);
7269
} else if (e.target.dropPosition === "after") {
73-
list.moveAfter(e.target.key, e.keys);
70+
moveAfter(e.target.key, e.keys);
7471
}
7572
},
7673
renderDragPreview,
@@ -81,11 +78,11 @@ function DraggableList({
8178
{...props}
8279
aria-label={props["aria-label"] ?? "Reorderable list"}
8380
selectionMode="single"
84-
items={list.items}
81+
items={list}
8582
dragAndDropHooks={dragDisabled ? undefined : dragAndDropHooks}
8683
onSelectionChange={(keys) => {
8784
const keyArr = Array.from(keys);
88-
const selectedItem = list.getItem(keyArr[0]);
85+
const selectedItem = getItem(keyArr[0]);
8986

9087
if (selectionCallback && selectedItem) selectionCallback(selectedItem);
9188
}}
@@ -96,50 +93,54 @@ function DraggableList({
9693
className,
9794
)}
9895
>
99-
{(item) => (
100-
<ListBoxItem
101-
textValue={item.name}
102-
className={({ isHovered, isDragging, isSelected }) =>
103-
cn(
104-
"h-11.25 w-full cursor-pointer border-l-3 border-l-transparent",
105-
"flex items-center gap-4 px-4",
106-
"focus-visible:outline-klerosUIComponentsPrimaryBlue focus-visible:outline",
107-
(isHovered || isSelected) && "bg-klerosUIComponentsMediumBlue",
108-
isSelected && "border-l-klerosUIComponentsPrimaryBlue",
109-
isDragging && "cursor-grabbing opacity-60",
110-
)
111-
}
112-
>
113-
{({ isHovered }) => (
114-
<>
115-
{dragDisabled ? null : (
116-
<DragAndDropIcon className="size-4 cursor-grab" />
117-
)}
118-
<span className="text-klerosUIComponentsPrimaryText flex-1 text-base">
119-
{item.name}
120-
</span>
121-
{isHovered && !deletionDisabled ? (
122-
<Button
123-
className={"cursor-pointer hover:scale-105"}
124-
onPress={() => {
125-
list.remove(item.id);
126-
}}
127-
>
128-
{({ isHovered: isButtonHovered }) => (
129-
<Trash
130-
className={clsx(
131-
"ease-ease size-4 transition",
132-
isButtonHovered &&
133-
"[&_path]:fill-klerosUIComponentsPrimaryBlue",
134-
)}
135-
/>
136-
)}
137-
</Button>
138-
) : null}
139-
</>
140-
)}
141-
</ListBoxItem>
142-
)}
96+
{list.map((item) => {
97+
return (
98+
<ListBoxItem
99+
id={item.id}
100+
key={item.id}
101+
textValue={item.name}
102+
className={({ isHovered, isDragging, isSelected }) =>
103+
cn(
104+
"h-11.25 w-full cursor-pointer border-l-3 border-l-transparent",
105+
"flex items-center gap-4 px-4",
106+
"focus-visible:outline-klerosUIComponentsPrimaryBlue focus-visible:outline",
107+
(isHovered || isSelected) && "bg-klerosUIComponentsMediumBlue",
108+
isSelected && "border-l-klerosUIComponentsPrimaryBlue",
109+
isDragging && "cursor-grabbing opacity-60",
110+
)
111+
}
112+
>
113+
{({ isHovered, isSelected }) => (
114+
<>
115+
{dragDisabled ? null : (
116+
<DragAndDropIcon className="size-4 cursor-grab" />
117+
)}
118+
<span className="text-klerosUIComponentsPrimaryText flex-1 text-base">
119+
{item.name}
120+
</span>
121+
{(isHovered || isSelected) && !deletionDisabled ? (
122+
<Button
123+
className={"cursor-pointer hover:scale-105"}
124+
onPress={() => {
125+
remove(item.id);
126+
}}
127+
>
128+
{({ isHovered: isButtonHovered }) => (
129+
<Trash
130+
className={clsx(
131+
"ease-ease size-4 transition",
132+
(isButtonHovered || isSelected) &&
133+
"[&_path]:fill-klerosUIComponentsPrimaryBlue",
134+
)}
135+
/>
136+
)}
137+
</Button>
138+
) : null}
139+
</>
140+
)}
141+
</ListBoxItem>
142+
);
143+
})}
143144
</ListBox>
144145
);
145146
}

src/lib/draggable-list/useList.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useCallback, useEffect, useMemo, useState } from "react";
2+
import _ from "lodash";
3+
4+
type Key = string | number;
5+
6+
export interface ListItem {
7+
id: Key;
8+
name: string;
9+
value: any;
10+
}
11+
12+
interface UseListOptions {
13+
initialItems: ListItem[];
14+
onChange?: (updatedItems: ListItem[]) => void;
15+
}
16+
17+
export function useList({ initialItems, onChange }: UseListOptions) {
18+
const [items, setItems] = useState<ListItem[]>(initialItems);
19+
20+
// track updates to initialItems
21+
useEffect(() => {
22+
setItems((prevItems) => {
23+
// preventing callback loop
24+
if (_.isEqual(initialItems, prevItems)) return prevItems;
25+
return initialItems;
26+
});
27+
}, [initialItems]);
28+
29+
const itemsMap = useMemo(() => {
30+
const map = new Map<Key, ListItem>();
31+
for (const item of items) {
32+
map.set(item.id, item);
33+
}
34+
return map;
35+
}, [items]);
36+
37+
const getItem = useCallback((key: Key) => itemsMap.get(key), [itemsMap]);
38+
39+
const remove = useCallback(
40+
(key: Key) => {
41+
// updateItems(items.filter((item) => key !== item.id));
42+
setItems((prevItems) => {
43+
const newItems = prevItems.filter((item) => key !== item.id);
44+
onChange?.(newItems);
45+
return newItems;
46+
});
47+
},
48+
[onChange],
49+
);
50+
51+
const moveBefore = useCallback(
52+
(targetKey: Key, keys: Iterable<Key>) => {
53+
setItems((prevItems) => {
54+
const key = Array.from(keys)[0];
55+
if (key === targetKey) return prevItems;
56+
57+
const indexFrom = prevItems.findIndex((item) => item.id === key);
58+
const indexTo = prevItems.findIndex((item) => item.id === targetKey);
59+
if (indexFrom === -1 || indexTo === -1) return prevItems;
60+
61+
const reordered = [...prevItems];
62+
const [movedItem] = reordered.splice(indexFrom, 1);
63+
reordered.splice(indexTo, 0, movedItem);
64+
onChange?.(reordered);
65+
66+
return reordered;
67+
});
68+
},
69+
[onChange],
70+
);
71+
72+
const moveAfter = useCallback(
73+
(targetKey: Key, keys: Iterable<Key>) => {
74+
setItems((prevItems) => {
75+
const key = Array.from(keys)[0];
76+
if (key === targetKey) return prevItems;
77+
78+
const indexFrom = prevItems.findIndex((item) => item.id === key);
79+
const indexTo = prevItems.findIndex((item) => item.id === targetKey);
80+
if (indexFrom === -1 || indexTo === -1) return prevItems;
81+
82+
const reordered = [...prevItems];
83+
const [movedItem] = reordered.splice(indexFrom, 1);
84+
85+
// Adjust if removing item before target index
86+
const insertIndex = indexFrom < indexTo ? indexTo : indexTo + 1;
87+
reordered.splice(insertIndex, 0, movedItem);
88+
onChange?.(reordered);
89+
90+
return reordered;
91+
});
92+
},
93+
[onChange],
94+
);
95+
96+
return {
97+
items,
98+
getItem,
99+
remove,
100+
moveBefore,
101+
moveAfter,
102+
};
103+
}

0 commit comments

Comments
 (0)