Skip to content

Chore/refactors and updates #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 11, 2025
16 changes: 12 additions & 4 deletions src/lib/button/ButtonIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ const ButtonIcon: React.FC<
Pick<ButtonProps, "Icon" | "icon" | "isDisabled" | "isLoading" | "variant">
> = ({ Icon, icon, isDisabled, isLoading, variant }) => {
const isSecondary = variant === "secondary";
return (
icon ??
(Icon && (
return icon ? (
isLoading ? (
<span
className={cn("button-svg", "mr-2 size-4", "invisible")}
aria-hidden
/>
) : (
icon
)
) : (
Icon && (
<Icon
className={cn(
"button-svg",
Expand All @@ -19,7 +27,7 @@ const ButtonIcon: React.FC<
isDisabled && ["fill-klerosUIComponentsStroke"],
)}
/>
))
)
);
};
export default ButtonIcon;
6 changes: 5 additions & 1 deletion src/lib/dropdown/cascader/dropdown-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface IDropdownContainer
"disabledKeys" | "defaultSelectedKey" | "items" | "callback"
> {
isOpen?: boolean;
className?: string;
}

const DropdownContainer: React.FC<IDropdownContainer> = ({
Expand All @@ -27,6 +28,7 @@ const DropdownContainer: React.FC<IDropdownContainer> = ({
disabledKeys,
defaultSelectedKey,
callback,
className,
}) => {
const gridRef = useRef<HTMLDivElement>(null);
const [selectedKey, setSelectedKey] = useState<Key | null>(
Expand Down Expand Up @@ -78,17 +80,19 @@ const DropdownContainer: React.FC<IDropdownContainer> = ({

return (
<Popover
className={clsx(
className={cn(
"bg-klerosUIComponentsWhiteBackground rounded-base border-klerosUIComponentsStroke box-border border",
"shadow-default focus-visible:outline-klerosUIComponentsPrimaryBlue overflow-hidden",
"w-60 origin-top transform transition lg:w-max lg:max-w-6xl",
isOpen
? "entering:animate-scale-in scale-y-100"
: "exiting:animate-scale-out scale-y-0",
className,
)}
>
<Dialog aria-label="dropdown-dialog">
<Tree
id="dropdown-tree"
ref={gridRef}
autoFocus="first"
items={items}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/dropdown/cascader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IDropdownCascader extends AriaSelectProps {
/** Callback function passes the Item object as argument. */
callback: (item: IItem) => void;
label?: string;
dropdownClassName?: string;
}

/** A Dropdown Cascader provides users with a way to navigate nested hierarchical information,
Expand All @@ -29,6 +30,7 @@ function DropdownCascader({
disabledKeys,
selectedKey,
defaultSelectedKey,
dropdownClassName,
...props
}: Readonly<IDropdownCascader>) {
return (
Expand Down Expand Up @@ -56,6 +58,7 @@ function DropdownCascader({
defaultSelectedKey: selectedKey ?? defaultSelectedKey,
disabledKeys,
callback,
className: dropdownClassName,
}}
/>
</>
Expand Down
14 changes: 9 additions & 5 deletions src/lib/dropdown/select/dropdown-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,28 @@ import React from "react";
import { Collection, ListBox, Popover } from "react-aria-components";
import Scrollbar from "../../scrollbar";
import Item, { IItem } from "./item";
import { cn } from "../../../utils";

const DropdownContainer: React.FC<{ isOpen?: boolean; items: IItem[] }> = ({
isOpen,
items,
}) => {
const DropdownContainer: React.FC<{
isOpen?: boolean;
items: IItem[];
className?: string;
}> = ({ isOpen, items, className }) => {
return (
<Popover
className={clsx(
className={cn(
"bg-klerosUIComponentsWhiteBackground rounded-base border-klerosUIComponentsStroke border",
"shadow-default focus:outline-klerosUIComponentsPrimaryBlue box-border overflow-hidden outline-none",
"origin-top transform transition",
isOpen
? "entering:animate-scale-in scale-y-100"
: "exiting:animate-scale-out scale-y-0",
className,
)}
>
<Scrollbar className="max-h-87.5 w-59.5">
<ListBox
id="listbox"
className={clsx(
"bg-klerosUIComponentsWhiteBackground box-border w-59.5",
"cols-[repeat(auto-fill,_minmax(0,_45px))] grid grow py-4",
Expand Down
6 changes: 5 additions & 1 deletion src/lib/dropdown/select/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface SelectProps extends AriaSelectProps {
/** When `simpleButton` is `true`, this scales down the dropdown button size. */
smallButton?: boolean;
label?: string;
dropdownClassName?: string;
}

/** A select displays a collapsible list of options and allows a user to select one of them. */
Expand All @@ -32,6 +33,7 @@ function DropdownSelect({
callback,
placeholder,
className,
dropdownClassName,
...props
}: Readonly<SelectProps>) {
const handleSelection = useCallback(
Expand Down Expand Up @@ -64,7 +66,9 @@ function DropdownSelect({
<DropdownButton {...{ placeholder }} />
)}
<FieldError className="text-klerosUIComponentsError text-sm" />
<DropdownContainer {...{ isOpen, items }} />
<DropdownContainer
{...{ isOpen, items, className: dropdownClassName }}
/>
</>
)}
</AriaSelect>
Expand Down
7 changes: 6 additions & 1 deletion src/lib/form/bignumber-field/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface BigNumberFieldComponentProps extends BigNumberFieldProps {
className?: string;
/** The name of the input element, used when submitting an HTML form.*/
name?: string;
inputRef?: React.Ref<HTMLInputElement>;
buttonRef?: React.Ref<HTMLButtonElement>;
}

/** A number field that handles big numbers.
Expand All @@ -31,6 +33,8 @@ function BigNumberField({
isDisabled,
isReadOnly,
name,
inputRef,
buttonRef,
...props
}: Readonly<BigNumberFieldComponentProps>) {
// Use our custom hook to get all the props and state
Expand All @@ -44,7 +48,6 @@ function BigNumberField({
errorMessageProps,
validationResult,
} = useBigNumberField({ isDisabled, placeholder, isReadOnly, ...props });

return (
<div className={cn("flex w-[278px] flex-col", className)}>
{label && (
Expand All @@ -64,6 +67,7 @@ function BigNumberField({
<>
<Input
{...inputProps}
ref={inputRef}
aria-errormessage={`BigNumberFieldError-${inputProps.id}`}
name={name}
className={cn(
Expand Down Expand Up @@ -98,6 +102,7 @@ function BigNumberField({
>
<Button
{...incrementButtonProps}
ref={buttonRef}
excludeFromTabOrder
className={clsx(
"rounded-base hover:bg-klerosUIComponentsStroke size-3.5 cursor-pointer border-none bg-transparent",
Expand Down
14 changes: 11 additions & 3 deletions src/lib/form/datepicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ interface DatePickerProps
/** Show time selection if true. */
time?: boolean;
label?: string;
popoverClassName?: string;
}

/** A date picker allow users to enter or select a date and time value. */
Expand All @@ -34,6 +35,7 @@ function DatePicker({
minValue,
defaultValue = now(getLocalTimeZone()),
shouldCloseOnSelect = false,
popoverClassName,
...props
}: Readonly<DatePickerProps>) {
return (
Expand All @@ -59,14 +61,20 @@ function DatePicker({

<Popover
className={clsx(
"bg-klerosUIComponentsWhiteBackground shadow-default rounded-base overflow-y-scroll",
"bg-klerosUIComponentsWhiteBackground shadow-default rounded-base overflow-scroll",
"border-klerosUIComponentsStroke ease-ease scrollbar border transition",
time ? "w-82.5 lg:w-112.5" : "w-82.5",
popoverClassName,
)}
>
<Dialog className="flex size-full flex-wrap">
<Calendar />
{time && <TimeControl {...{ minValue }} />}
<div
className={clsx("flex", time ? "w-82.5 lg:w-112.5" : "w-82.5")}
>
<Calendar />
{time && <TimeControl {...{ minValue }} />}
</div>

<div
className={clsx(
"flex h-16 w-full items-center justify-between px-4",
Expand Down
27 changes: 22 additions & 5 deletions src/lib/form/file-uploader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import UploadIcon from "../../assets/svgs/form/upload-icon.svg";
import SuccessIcon from "../../assets/svgs/status-icons/success.svg";
import ErrorIcon from "../../assets/svgs/status-icons/error.svg";
Expand All @@ -25,6 +25,10 @@ interface FileUploaderProps {
/** Whether the drop target is disabled. If true, the drop target will not accept any drops. */
isDisabled?: boolean;
className?: string;
/** Provide a custom validation function, returning false invalidates the file */
validationFunction?: (file?: File) => boolean;
/** Provide File for controlled behaviour */
selectedFile?: File;
}

/** Allows to upload a file by either dropping it on the dropzone or
Expand All @@ -37,8 +41,16 @@ function FileUploader({
acceptedFileTypes,
fileTriggerProps,
isDisabled = false,
validationFunction,
selectedFile,
}: Readonly<FileUploaderProps>) {
const [fileSelected, setFileSelected] = useState<File>();
const [fileSelected, setFileSelected] = useState<File | undefined>(
selectedFile,
);

useEffect(() => {
setFileSelected(selectedFile);
}, [selectedFile]);

return (
<div className={cn("box-border h-fit w-50", className)}>
Expand Down Expand Up @@ -71,7 +83,10 @@ function FileUploader({

if (item) {
const file = await item.getFile();
setFileSelected(file);
const validated = validationFunction?.(file) ?? true;
if (!validated) return;
if (selectedFile === undefined) setFileSelected(file);

callback(file);
}
}}
Expand All @@ -82,7 +97,9 @@ function FileUploader({
onSelect={(e) => {
if (e) {
const file = e[0];
setFileSelected(file);
const validated = validationFunction?.(file) ?? true;
if (!validated) return;
if (selectedFile === undefined) setFileSelected(file);
callback(file);
}
}}
Expand All @@ -109,7 +126,7 @@ function FileUploader({
</FileTrigger>
</DropZone>
{msg && (
<div className="mt-4 flex items-start">
<div className="mt-4 flex items-center">
{variant === "success" && (
<SuccessIcon className="fill-klerosUIComponentsSuccess mr-2 max-w-4 min-w-4" />
)}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/progress/timeline/custom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import Bullet, { StateProp, VariantProp } from "./bullet";
import { cn } from "../../../utils";

interface TimelineItem extends VariantProp, StateProp {
export interface TimelineItem extends VariantProp, StateProp {
title: string;
party: string | React.ReactElement;
subtitle: string;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/progress/timeline/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import Bullet, { SideProp, VariantProp } from "./bullet";
import clsx from "clsx";
import { cn } from "../../../utils";

interface TimelineItem extends SideProp, VariantProp {
export interface TimelineItem extends SideProp, VariantProp {
title: string;
party: string;
subtitle: string;
}

interface TimelineProps {
export interface TimelineProps {
items: [TimelineItem, ...TimelineItem[]];
className?: string;
}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/tooltip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ function Tooltip({
return (
<TooltipTrigger {...{ delay }} {...props}>
<Focusable>
<div {...props.wrapperProps}>{children}</div>
<div
{...props.wrapperProps}
role={props?.wrapperProps?.role ?? "button"}
>
{children}
</div>
</Focusable>
<AriaTooltip
offset={props.tooltipProps?.offset ?? 8}
Expand Down
42 changes: 42 additions & 0 deletions src/stories/dropdown-select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IPreviewArgs } from "./utils";
import SelectComponent from "../lib/dropdown/select";
import { Form } from "react-aria-components";
import { Button } from "../lib";
import Telegram from "../assets/svgs/telegram.svg";

const meta = {
component: SelectComponent,
Expand Down Expand Up @@ -109,3 +110,44 @@ export const RequiredSelect: Story = {
</Form>
),
};

export const CustomItemIcon: Story = {
args: {
themeUI: "dark",
backgroundUI: "light",
items: [
{
text: "hello 1",
itemValue: 1,
id: 1,
icon: <Telegram className="mr-2 size-4 fill-white" />,
},
{
text: "hello 2",
itemValue: 2,
id: 2,
icon: <Telegram className="mr-2 size-4 fill-white" />,
},
{
text: "hello 3",
itemValue: 3,
id: 3,
icon: <Telegram className="mr-2 size-4 fill-white" />,
},
{
text: "hello 4",
itemValue: 4,
id: 4,
icon: <Telegram className="mr-2 size-4 fill-white" />,
},
{
text: "hello 5",
itemValue: 5,
id: 5,
icon: <Telegram className="mr-2 size-4 fill-white" />,
},
],
placeholder: "Select a value",
callback: () => {},
},
};
Loading