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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ If you wish the use the library's tailwind theme variables in your tailwind app.

```css
@import tailwindcss @import
"../../../node_modules/@kleros/ui-components-library/dist/theme/theme.css";
"../../../node_modules/@kleros/ui-components-library/dist/assets/theme.css";
```

You can find the available theme variables [here](src/styles/theme.css).
Expand Down
26 changes: 15 additions & 11 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"version": "3.1.2",
"description": "UI components library which implements the Kleros design system.",
"source": "./src/lib/index.ts",
"main": "./dist/esm/ui-components-library.js",
"module": "./dist/esm/ui-components-library.js",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/ui-components-library.d.ts",
"style": "./dist/esm/ui-components-library.css",
"style": "./dist/assets/index.css",
"isLibrary": true,
"type": "module",
"browserslist": "> 0.5%, last 2 versions, not dead",
Expand All @@ -15,15 +15,17 @@
"license": "MIT",
"exports": {
".": {
"import": "./dist/esm/ui-components-library.js",
"import": "./dist/index.js",
"types": "./dist/ui-components-library.d.ts"
},
"./style.css": "./dist/esm/ui-components-library.css",
"./theme.css": "./dist/theme/theme.css"
"./style.css": "./dist/assets/index.css",
"./theme.css": "./dist/assets/theme.css"
},
"sideEffects": [
"**/*.css"
],
"scripts": {
"build:theme": "vite build --config vite.config.theme.js",
"build": "vite build && yarn build:theme",
"build": "vite build",
"build-storybook": "storybook build",
"check-style": "eslint 'src/**/*.{ts,tsx}'",
"check-types": "tsc --noEmit",
Expand All @@ -49,6 +51,7 @@
"@storybook/react-vite": "^8.6.4",
"@storybook/test": "^8.6.4",
"@tailwindcss/postcss": "^4.0.11",
"@tailwindcss/vite": "^4.1.4",
"@types/node": "^22.13.10",
"@types/react": "^18.0.9",
"@types/react-dom": "^18.0.3",
Expand All @@ -64,6 +67,7 @@
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-storybook": "^0.11.4",
"glob": "^11.0.1",
"globals": "^16.0.0",
"husky": "^7.0.0",
"lint-staged": "^12.1.2",
Expand All @@ -74,6 +78,8 @@
"tailwindcss": "^4.0.11",
"typescript": "^5.8.2",
"vite": "^6.2.1",
"vite-plugin-dts": "^4.5.3",
"vite-plugin-lib-inject-css": "^2.2.2",
"vite-plugin-svgr": "^4.3.0"
},
"peerDependencies": {
Expand All @@ -85,7 +91,6 @@
},
"dependencies": {
"@internationalized/date": "^3.7.0",
"@tailwindcss/vite": "^4.0.12",
"bignumber.js": "^9.1.2",
"clsx": "^2.1.1",
"react": "^18.0.0",
Expand All @@ -96,8 +101,7 @@
"simplebar-react": "^2.3.6",
"tailwind-merge": "^3.0.2",
"tailwindcss-react-aria-components": "^2.0.0",
"usehooks-ts": "^2.9.1",
"vite-plugin-dts": "^4.5.3"
"usehooks-ts": "^2.9.1"
},
"lint-staged": {
"*.js": "eslint --cache --fix",
Expand Down
3 changes: 3 additions & 0 deletions src/assets/svgs/drag-and-drop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/svgs/trash.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/lib/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Button } from "react-aria-components";
import { clsx } from "clsx";

interface BreadcrumbProps {
items: { text: string; value: any }[];
items: { text: React.ReactNode; value: any }[];
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
callback?: Function;
clickable?: boolean;
Expand Down
139 changes: 139 additions & 0 deletions src/lib/draggable-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, { useEffect } from "react";
import { useListData } from "react-stately";
import {
Button,
ListBox,
ListBoxItem,
useDragAndDrop,
type ListBoxItemProps,
type ListBoxProps,
type DragAndDropOptions,
} from "react-aria-components";
import { cn } from "../../utils";
import DragAndDropIcon from "../../assets/svgs/drag-and-drop.svg";
import Trash from "../../assets/svgs/trash.svg";
import clsx from "clsx";

type ListItem = {
id: string | number;
name: string;
value: any;
};
interface IDraggableList
extends Omit<
ListBoxProps<ListBoxItemProps>,
| "items"
| "selectionMode"
| "dragAndDropHooks"
| "selectionBehavior"
| "orientation"
| "onSelectionChange"
> {
items: ListItem[];
/** Returns the updated list after a delete or move operation. */
updateCallback?: (updatedItems: ListItem[]) => void;
/** Returns the selected item. */
selectionCallback?: (list: ListItem) => void;
/** Display custom preview for the item being dragged. */
renderDragPreview?: DragAndDropOptions["renderDragPreview"];
}

/** List that allows users to reorder items via drag and drop */
function DraggableList({
items,
updateCallback,
selectionCallback,
className,
renderDragPreview,
...props
}: Readonly<IDraggableList>) {
const list = useListData({
initialItems: items,
});

useEffect(() => {
if (!updateCallback) return;
updateCallback(list.items);
}, [list, updateCallback, items]);

const { dragAndDropHooks } = useDragAndDrop({
getItems: (keys) =>
[...keys].map((key) => ({ "text/plain": list.getItem(key)!.name })),
getAllowedDropOperations: () => ["move"],
onReorder(e) {
if (e.target.dropPosition === "before") {
list.moveBefore(e.target.key, e.keys);
} else if (e.target.dropPosition === "after") {
list.moveAfter(e.target.key, e.keys);
}
},
renderDragPreview,
});

return (
<ListBox
{...props}
aria-label={props["aria-label"] ?? "Reorderable list"}
selectionMode="single"
items={list.items}
dragAndDropHooks={dragAndDropHooks}
onSelectionChange={(keys) => {
const keyArr = Array.from(keys);
const selectedItem = list.getItem(keyArr[0]);

if (selectionCallback && selectedItem) selectionCallback(selectedItem);
}}
className={cn(
"bg-klerosUIComponentsLightBackground rounded-base border-klerosUIComponentsStroke border",
"w-95.5 py-4",
"[&_div]:data-drop-target:outline-klerosUIComponentsPrimaryBlue [&_div]:data-drop-target:outline",
className,
)}
>
{(item) => (
<ListBoxItem
textValue={item.name}
className={({ isHovered, isDragging, isSelected }) =>
cn(
"h-11.25 w-full cursor-pointer border-l-3 border-l-transparent",
"flex items-center gap-4 px-4",
"focus-visible:outline-klerosUIComponentsPrimaryBlue focus-visible:outline",
(isHovered || isSelected) && "bg-klerosUIComponentsMediumBlue",
isSelected && "border-l-klerosUIComponentsPrimaryBlue",
isDragging && "cursor-grabbing opacity-60",
)
}
>
{({ isHovered }) => (
<>
<DragAndDropIcon className="size-4 cursor-grab" />
<span className="text-klerosUIComponentsPrimaryText flex-1 text-base">
{item.name}
</span>
{isHovered ? (
<Button
className={"cursor-pointer hover:scale-105"}
onPress={() => {
list.remove(item.id);
}}
>
{({ isHovered: isButtonHovered }) => (
<Trash
className={clsx(
"ease-ease size-4 transition",
isButtonHovered &&
"[&_path]:fill-klerosUIComponentsPrimaryBlue",
)}
/>
)}
</Button>
) : null}
</>
)}
</ListBoxItem>
)}
</ListBox>
);
}

export default DraggableList;
2 changes: 2 additions & 0 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ export { default as Tooltip } from "../lib/tooltip";
export { default as ScrollbarContainer } from "../lib/scrollbar";

export { default as Copiable } from "../lib/copiable";

export { default as DraggableList } from "../lib/draggable-list";
53 changes: 53 additions & 0 deletions src/stories/draggable-list.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";

import { IPreviewArgs } from "./utils";

import DraggableList from "../lib/draggable-list";

const meta = {
component: DraggableList,
title: "Draggable List",
tags: ["autodocs"],
} satisfies Meta<typeof DraggableList>;

export default meta;

type Story = StoryObj<typeof meta> & IPreviewArgs;

export const Default: Story = {
args: {
themeUI: "light",
backgroundUI: "light",
items: [
{ id: 1, name: "Illustrator", value: "" },
{ id: 2, name: "Premiere", value: "" },
{ id: 3, name: "Acrobat", value: "" },
],
},
render: (args) => {
return <DraggableList {...args} />;
},
};

export const CustomDragPreview: Story = {
args: {
themeUI: "light",
backgroundUI: "light",
items: [
{ id: 1, name: "Illustrator", value: "" },
{ id: 2, name: "Premiere", value: "" },
{ id: 3, name: "Acrobat", value: "" },
],
renderDragPreview: (items) => (
<div className="rounded-base bg-klerosUIComponentsPrimaryBlue px-4 py-2">
<span className="text-klerosUIComponentsPrimaryText text-base">
{items[0]["text/plain"]}
</span>
</div>
),
},
render: (args) => {
return <DraggableList {...args} />;
},
};
16 changes: 0 additions & 16 deletions vite.config.theme.ts

This file was deleted.

28 changes: 25 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { resolve } from "path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import svgr from "vite-plugin-svgr";
// eslint-disable-next-line import/no-unresolved
import tailwindcss from "@tailwindcss/vite";
import dts from "vite-plugin-dts";
import { libInjectCss } from "vite-plugin-lib-inject-css";
import { extname, relative, resolve } from "path";
import { fileURLToPath } from "node:url";
import { glob } from "glob";

export default defineConfig({
build: {
Expand All @@ -17,9 +20,27 @@ export default defineConfig({
// make sure to externalize deps that shouldn't be bundled
// into your library
external: ["react", "react-dom"],
input: Object.fromEntries(
glob
.sync("src/lib/**/*.{ts,tsx}", {
ignore: ["src/lib/**/*.d.ts"],
})
.map((file) => [
// The name of the entry point
// lib/nested/foo.ts becomes nested/foo
relative(
"src/lib",
file.slice(0, file.length - extname(file).length),
),
// The absolute path to the entry file
// lib/nested/foo.ts becomes /project/lib/nested/foo.ts
fileURLToPath(new URL(file, import.meta.url)),
]),
),
output: {
dir: "dist/esm",
preserveModules: true,
assetFileNames: "assets/[name][extname]",
entryFileNames: "[name].js",

// Provide global variables to use in the UMD build
// for externalized deps
globals: {
Expand All @@ -35,6 +56,7 @@ export default defineConfig({
}),
tailwindcss(),
react(),
libInjectCss(),
dts({
insertTypesEntry: true,
include: ["src/lib", "src/global.d.ts"],
Expand Down
Loading