@@ -176,7 +179,7 @@ const ChartTooltipContent = React.forwardRef<
svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
+ "flex w-full [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground gap-4 px-4",
indicator === "dot" && "items-center",
)}
>
@@ -210,21 +213,26 @@ const ChartTooltipContent = React.forwardRef<
)}
-
+
{nestLabel ? tooltipLabel : null}
-
+
+ {itemConfig?.subLabel}
+
+
{itemConfig?.label || item.name}
- {item.value && (
-
- {item.value.toLocaleString()}
-
- )}
+
+ {item.value && (
+
+ {item.value.toLocaleString()}
+
+ )}
+
>
)}
@@ -232,6 +240,7 @@ const ChartTooltipContent = React.forwardRef<
);
})}
+ {bottomExplainer}
);
},
diff --git a/apps/dashboard/components/virtual-table.tsx b/apps/dashboard/components/virtual-table.tsx
deleted file mode 100644
index ec0cb6363e..0000000000
--- a/apps/dashboard/components/virtual-table.tsx
+++ /dev/null
@@ -1,338 +0,0 @@
-import { Card, CardContent } from "@/components/ui/card";
-import { cn, throttle } from "@/lib/utils";
-import { useVirtualizer } from "@tanstack/react-virtual";
-import { ScrollText } from "lucide-react";
-import type React from "react";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { useScrollLock } from "usehooks-ts";
-
-export type Column
= {
- key: string;
- header: string;
- width: string;
- render: (item: T) => React.ReactNode;
-};
-
-export type VirtualTableProps = {
- data: T[];
- columns: Column[];
- isLoading?: boolean;
- rowHeight?: number;
- onRowClick?: (item: T | null) => void;
- onLoadMore?: () => void;
- emptyState?: React.ReactNode;
- loadingRows?: number;
- overscanCount?: number;
- keyExtractor: (item: T) => string | number;
- rowClassName?: (item: T) => string;
- selectedClassName?: (item: T, isSelected: boolean) => string;
- selectedItem?: T | null;
- isFetchingNextPage?: boolean;
- renderDetails?: (item: T, onClose: () => void, distanceToTop: number) => React.ReactNode;
-};
-
-const DEFAULT_ROW_HEIGHT = 26;
-const DEFAULT_LOADING_ROWS = 50;
-const DEFAULT_OVERSCAN = 5;
-const TABLE_BORDER_THICKNESS = 1;
-const THROTTLE_DELAY = 350;
-const HEADER_HEIGHT = 40; // Approximate height of the header
-
-export function VirtualTable({
- data,
- columns,
- isLoading = false,
- rowHeight = DEFAULT_ROW_HEIGHT,
- onRowClick,
- onLoadMore,
- emptyState,
- loadingRows = DEFAULT_LOADING_ROWS,
- overscanCount = DEFAULT_OVERSCAN,
- keyExtractor,
- rowClassName,
- selectedClassName,
- selectedItem,
- renderDetails,
- isFetchingNextPage,
-}: VirtualTableProps) {
- const parentRef = useRef(null);
- const tableRef = useRef(null);
- const containerRef = useRef(null);
- const [tableDistanceToTop, setTableDistanceToTop] = useState(0);
- const [fixedHeight, setFixedHeight] = useState(0);
-
- // We have to lock the wrapper div at layout, otherwise causes weird scrolling issues.
- useScrollLock({
- autoLock: true,
- lockTarget:
- typeof window !== "undefined"
- ? (document.querySelector("#layout-wrapper") as HTMLElement)
- : undefined,
- });
-
- // Calculate and set fixed height on mount and resize
- useEffect(() => {
- const calculateHeight = () => {
- if (!containerRef.current) {
- return;
- }
-
- const rect = containerRef.current.getBoundingClientRect();
- const headerHeight = HEADER_HEIGHT + TABLE_BORDER_THICKNESS;
- const availableHeight = window.innerHeight - rect.top - headerHeight;
-
- setFixedHeight(Math.max(availableHeight, 0));
- };
-
- calculateHeight();
-
- const resizeObserver = new ResizeObserver(calculateHeight);
- window.addEventListener("resize", calculateHeight);
-
- if (containerRef.current) {
- resizeObserver.observe(containerRef.current);
- }
-
- return () => {
- resizeObserver.disconnect();
- window.removeEventListener("resize", calculateHeight);
- };
- }, []);
-
- // biome-ignore lint/correctness/useExhaustiveDependencies: No need to add more deps
- const throttledLoadMore = useCallback(
- throttle(
- () => {
- if (onLoadMore) {
- onLoadMore();
- }
- },
- THROTTLE_DELAY,
- { leading: true, trailing: false },
- ),
- [onLoadMore],
- );
-
- useEffect(() => {
- return () => {
- throttledLoadMore.cancel();
- };
- }, [throttledLoadMore]);
-
- const virtualizer = useVirtualizer({
- count: isLoading ? loadingRows : data.length,
- getScrollElement: () => parentRef.current,
- estimateSize: () => rowHeight,
- overscan: overscanCount,
- onChange: (instance) => {
- const lastItem = instance.getVirtualItems().at(-1);
- if (!lastItem || !onLoadMore) {
- return;
- }
-
- const scrollElement = instance.scrollElement;
- if (!scrollElement) {
- return;
- }
-
- const scrollOffset = scrollElement.scrollTop + scrollElement.clientHeight;
- const scrollThreshold = scrollElement.scrollHeight - rowHeight * 3;
-
- if (
- !isLoading &&
- !isFetchingNextPage &&
- lastItem.index >= data.length - 1 - instance.options.overscan &&
- scrollOffset >= scrollThreshold
- ) {
- throttledLoadMore();
- }
- },
- });
-
- const handleRowClick = (item: T) => {
- if (onRowClick) {
- onRowClick(item);
- setTableDistanceToTop(
- (tableRef.current?.getBoundingClientRect().top ?? 0) +
- window.scrollY -
- TABLE_BORDER_THICKNESS,
- );
- }
- };
-
- const LoadingRow = () => (
- col.width).join(" ") }}
- >
- {columns.map((column) => (
-
- ))}
-
- );
-
- const TableHeader = () => (
- <>
- col.width).join(" "),
- }}
- >
- {columns.map((column) => (
-
- ))}
-
-
- >
- );
-
- if (!isLoading && data.length === 0) {
- return (
-
-
-
- {emptyState || (
-
-
-
- No data available
-
-
- )}
-
-
- );
- }
-
- return (
-
-
-
{
- if (el) {
- //@ts-expect-error safe to bypass
- parentRef.current = el;
- //@ts-expect-error safe to bypass
- tableRef.current = el;
- }
- }}
- data-table-container="true"
- className="overflow-auto pb-10"
- style={{ height: `${fixedHeight}px` }}
- >
-
- {virtualizer.getVirtualItems().map((virtualRow) => {
- if (isLoading) {
- return (
-
-
-
- );
- }
-
- const item = data[virtualRow.index];
- if (!item) {
- return null;
- }
-
- const isSelected = selectedItem
- ? keyExtractor(selectedItem) === keyExtractor(item)
- : false;
-
- return (
-
handleRowClick(item)}
- tabIndex={virtualRow.index}
- aria-selected={isSelected}
- onKeyDown={(event) => {
- if (event.key === "Escape" && onRowClick) {
- onRowClick(null);
- }
- if (event.key === "Enter" || event.key === " ") {
- event.preventDefault();
- handleRowClick(item);
- }
- if (event.key === "ArrowDown") {
- event.preventDefault();
- const nextElement = document.querySelector(
- `[data-index="${virtualRow.index + 1}"]`,
- ) as HTMLElement;
- nextElement?.focus();
- }
- if (event.key === "ArrowUp") {
- event.preventDefault();
- const prevElement = document.querySelector(
- `[data-index="${virtualRow.index - 1}"]`,
- ) as HTMLElement;
- prevElement?.focus();
- }
- }}
- className={cn(
- "grid text-[13px] leading-[14px] mb-[1px] rounded-[5px] cursor-pointer absolute top-0 left-0 w-full hover:bg-accent-3 pl-1 group",
- rowClassName?.(item),
- selectedItem && {
- "opacity-50": !isSelected,
- "opacity-100": isSelected,
- },
- selectedClassName?.(item, isSelected),
- )}
- style={{
- gridTemplateColumns: columns.map((col) => col.width).join(" "),
- height: `${rowHeight}px`,
- top: `${virtualRow.start}px`,
- }}
- >
- {columns.map((column) => (
-
-
{column.render(item)}
-
- ))}
-
- );
- })}
-
-
- {isFetchingNextPage && (
-
- )}
-
- {selectedItem &&
- renderDetails &&
- renderDetails(selectedItem, () => onRowClick?.(null as any), tableDistanceToTop)}
-
-
- );
-}
diff --git a/apps/dashboard/components/virtual-table/components/empty-state.tsx b/apps/dashboard/components/virtual-table/components/empty-state.tsx
new file mode 100644
index 0000000000..476b4d1bfd
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/components/empty-state.tsx
@@ -0,0 +1,15 @@
+import { Card, CardContent } from "@/components/ui/card";
+import { ScrollText } from "lucide-react";
+
+export const EmptyState = ({ content }: { content?: React.ReactNode }) => (
+
+ {content || (
+
+
+
+ No data available
+
+
+ )}
+
+);
diff --git a/apps/dashboard/components/virtual-table/components/loading-indicator.tsx b/apps/dashboard/components/virtual-table/components/loading-indicator.tsx
new file mode 100644
index 0000000000..469178a8b2
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/components/loading-indicator.tsx
@@ -0,0 +1,8 @@
+export const LoadingIndicator = () => (
+
+);
diff --git a/apps/dashboard/components/virtual-table/components/loading-row.tsx b/apps/dashboard/components/virtual-table/components/loading-row.tsx
new file mode 100644
index 0000000000..bdf69c2070
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/components/loading-row.tsx
@@ -0,0 +1,18 @@
+import type { Column } from "../types";
+
+export const LoadingRow = ({
+ columns,
+}: {
+ columns: Column[];
+}) => (
+ col.width).join(" ") }}
+ >
+ {columns.map((column) => (
+
+ ))}
+
+);
diff --git a/apps/dashboard/components/virtual-table/components/table-header.tsx b/apps/dashboard/components/virtual-table/components/table-header.tsx
new file mode 100644
index 0000000000..f864fbce87
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/components/table-header.tsx
@@ -0,0 +1,23 @@
+import type { Column } from "../types";
+
+export const TableHeader = ({
+ columns,
+}: {
+ columns: Column[];
+}) => (
+ <>
+ col.width).join(" "),
+ }}
+ >
+ {columns.map((column) => (
+
+ ))}
+
+
+ >
+);
diff --git a/apps/dashboard/components/virtual-table/components/table-row.tsx b/apps/dashboard/components/virtual-table/components/table-row.tsx
new file mode 100644
index 0000000000..eca759d806
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/components/table-row.tsx
@@ -0,0 +1,83 @@
+import type { VirtualItem } from "@tanstack/react-virtual";
+import { cn } from "@unkey/ui/src/lib/utils";
+import type { Column } from "../types";
+
+export const TableRow = ({
+ item,
+ columns,
+ virtualRow,
+ rowHeight,
+ isSelected,
+ rowClassName,
+ selectedClassName,
+ onClick,
+ measureRef,
+ onRowClick,
+}: {
+ item: T;
+ columns: Column[];
+ virtualRow: VirtualItem;
+ rowHeight: number;
+ isSelected: boolean;
+ rowClassName?: (item: T) => string;
+ selectedClassName?: (item: T, isSelected: boolean) => string;
+ onClick: () => void;
+ onRowClick?: (item: T | null) => void;
+ measureRef: (element: HTMLElement | null) => void;
+}) => (
+ {
+ if (event.key === "Escape") {
+ event.preventDefault();
+ onRowClick?.(null);
+ const activeElement = document.activeElement as HTMLElement;
+ activeElement?.blur();
+ }
+
+ if (event.key === "ArrowDown" || event.key === "j") {
+ event.preventDefault();
+ const nextElement = document.querySelector(
+ `[data-index="${virtualRow.index + 1}"]`,
+ ) as HTMLElement;
+ if (nextElement) {
+ nextElement.focus();
+ nextElement.click(); // This will trigger onClick which calls handleRowClick
+ }
+ }
+ if (event.key === "ArrowUp" || event.key === "k") {
+ event.preventDefault();
+ const prevElement = document.querySelector(
+ `[data-index="${virtualRow.index - 1}"]`,
+ ) as HTMLElement;
+ if (prevElement) {
+ prevElement.focus();
+ prevElement.click(); // This will trigger onClick which calls handleRowClick
+ }
+ }
+ }}
+ ref={measureRef}
+ onClick={onClick}
+ className={cn(
+ "grid text-xs cursor-pointer absolute top-0 left-0 w-full",
+ "transition-all duration-75 ease-in-out",
+ "hover:bg-accent-3 ",
+ "group rounded-md",
+ rowClassName?.(item),
+ selectedClassName?.(item, isSelected),
+ )}
+ style={{
+ gridTemplateColumns: columns.map((col) => col.width).join(" "),
+ height: `${rowHeight}px`,
+ top: `${virtualRow.start}px`,
+ }}
+ >
+ {columns.map((column) => (
+
+ {column.render(item)}
+
+ ))}
+
+);
diff --git a/apps/dashboard/components/virtual-table/constants.ts b/apps/dashboard/components/virtual-table/constants.ts
new file mode 100644
index 0000000000..bb044d355f
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/constants.ts
@@ -0,0 +1,10 @@
+import type { TableConfig } from "./types";
+
+export const DEFAULT_CONFIG: TableConfig = {
+ rowHeight: 26,
+ loadingRows: 50,
+ overscan: 5,
+ tableBorder: 1,
+ throttleDelay: 350,
+ headerHeight: 40,
+} as const;
diff --git a/apps/dashboard/components/virtual-table/hooks/useTableHeight.ts b/apps/dashboard/components/virtual-table/hooks/useTableHeight.ts
new file mode 100644
index 0000000000..08c2b8fe97
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/hooks/useTableHeight.ts
@@ -0,0 +1,36 @@
+import { useEffect, useState } from "react";
+
+export const useTableHeight = (
+ containerRef: React.RefObject,
+ headerHeight: number,
+ tableBorder: number,
+) => {
+ const [fixedHeight, setFixedHeight] = useState(0);
+
+ useEffect(() => {
+ const calculateHeight = () => {
+ if (!containerRef.current) {
+ return;
+ }
+ const rect = containerRef.current.getBoundingClientRect();
+ const totalHeaderHeight = headerHeight + tableBorder;
+ const availableHeight = window.innerHeight - rect.top - totalHeaderHeight;
+ setFixedHeight(Math.max(availableHeight, 0));
+ };
+
+ calculateHeight();
+ const resizeObserver = new ResizeObserver(calculateHeight);
+ window.addEventListener("resize", calculateHeight);
+
+ if (containerRef.current) {
+ resizeObserver.observe(containerRef.current);
+ }
+
+ return () => {
+ resizeObserver.disconnect();
+ window.removeEventListener("resize", calculateHeight);
+ };
+ }, [containerRef, headerHeight, tableBorder]);
+
+ return fixedHeight;
+};
diff --git a/apps/dashboard/components/virtual-table/hooks/useVirtualData.ts b/apps/dashboard/components/virtual-table/hooks/useVirtualData.ts
new file mode 100644
index 0000000000..3af3e19832
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/hooks/useVirtualData.ts
@@ -0,0 +1,65 @@
+import { throttle } from "@/lib/utils";
+import { useVirtualizer } from "@tanstack/react-virtual";
+import { useCallback, useEffect } from "react";
+import type { TableConfig } from "../types";
+
+export const useVirtualData = ({
+ data,
+ isLoading,
+ config,
+ onLoadMore,
+ isFetchingNextPage,
+ parentRef,
+}: {
+ data: T[];
+ isLoading: boolean;
+ config: TableConfig;
+ onLoadMore?: () => void;
+ isFetchingNextPage?: boolean;
+ parentRef: React.RefObject;
+}) => {
+ // biome-ignore lint/correctness/useExhaustiveDependencies: fine to use config.throttleDelay
+ const throttledLoadMore = useCallback(
+ throttle(onLoadMore ?? (() => {}), config.throttleDelay, {
+ leading: true,
+ trailing: false,
+ }),
+ [onLoadMore, config.throttleDelay],
+ );
+
+ useEffect(() => {
+ return () => {
+ throttledLoadMore.cancel();
+ };
+ }, [throttledLoadMore]);
+
+ return useVirtualizer({
+ count: isLoading ? config.loadingRows : data.length,
+ getScrollElement: () => parentRef.current,
+ estimateSize: () => config.rowHeight,
+ overscan: config.overscan,
+ onChange: (instance) => {
+ const lastItem = instance.getVirtualItems().at(-1);
+ if (!lastItem || !onLoadMore) {
+ return;
+ }
+
+ const scrollElement = instance.scrollElement;
+ if (!scrollElement) {
+ return;
+ }
+
+ const scrollOffset = scrollElement.scrollTop + scrollElement.clientHeight;
+ const scrollThreshold = scrollElement.scrollHeight - config.rowHeight * 3;
+
+ if (
+ !isLoading &&
+ !isFetchingNextPage &&
+ lastItem.index >= data.length - 1 - instance.options.overscan &&
+ scrollOffset >= scrollThreshold
+ ) {
+ throttledLoadMore();
+ }
+ },
+ });
+};
diff --git a/apps/dashboard/components/virtual-table/index.tsx b/apps/dashboard/components/virtual-table/index.tsx
new file mode 100644
index 0000000000..0c80fae929
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/index.tsx
@@ -0,0 +1,149 @@
+import { cn } from "@unkey/ui/src/lib/utils";
+import { useCallback, useRef, useState } from "react";
+import { useScrollLock } from "usehooks-ts";
+import { EmptyState } from "./components/empty-state";
+import { LoadingIndicator } from "./components/loading-indicator";
+import { LoadingRow } from "./components/loading-row";
+import { TableHeader } from "./components/table-header";
+import { TableRow } from "./components/table-row";
+import { DEFAULT_CONFIG } from "./constants";
+import { useTableHeight } from "./hooks/useTableHeight";
+import { useVirtualData } from "./hooks/useVirtualData";
+import type { VirtualTableProps } from "./types";
+
+export function VirtualTable({
+ data,
+ columns,
+ isLoading = false,
+ config: userConfig,
+ onRowClick,
+ onLoadMore,
+ emptyState,
+ keyExtractor,
+ rowClassName,
+ selectedClassName,
+ selectedItem,
+ renderDetails,
+ isFetchingNextPage,
+}: VirtualTableProps) {
+ const config = { ...DEFAULT_CONFIG, ...userConfig };
+ const parentRef = useRef(null);
+ const containerRef = useRef(null);
+ const [tableDistanceToTop, setTableDistanceToTop] = useState(0);
+
+ const fixedHeight = useTableHeight(containerRef, config.headerHeight, config.tableBorder);
+ const virtualizer = useVirtualData({
+ data,
+ isLoading,
+ config,
+ onLoadMore,
+ isFetchingNextPage,
+ parentRef,
+ });
+
+ useScrollLock({
+ autoLock: true,
+ lockTarget:
+ typeof window !== "undefined"
+ ? (document.querySelector("#layout-wrapper") as HTMLElement)
+ : undefined,
+ });
+
+ const handleRowClick = useCallback(
+ (item: T) => {
+ if (onRowClick) {
+ onRowClick(item);
+ setTableDistanceToTop(
+ (parentRef.current?.getBoundingClientRect().top ?? 0) +
+ window.scrollY -
+ config.tableBorder,
+ );
+ }
+ },
+ [onRowClick, config.tableBorder],
+ );
+
+ if (!isLoading && data.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+ {virtualizer.getVirtualItems().map((virtualRow) => {
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ const item = data[virtualRow.index];
+ if (!item) {
+ return null;
+ }
+
+ const isSelected = selectedItem
+ ? keyExtractor(selectedItem) === keyExtractor(item)
+ : false;
+
+ return (
+
handleRowClick(item)}
+ measureRef={virtualizer.measureElement}
+ onRowClick={onRowClick}
+ />
+ );
+ })}
+
+
+ {isFetchingNextPage &&
}
+
+ {selectedItem &&
+ renderDetails &&
+ renderDetails(selectedItem, () => onRowClick?.(null as any), tableDistanceToTop)}
+
+
+ );
+}
diff --git a/apps/dashboard/components/virtual-table/types.ts b/apps/dashboard/components/virtual-table/types.ts
new file mode 100644
index 0000000000..9fd4949cf3
--- /dev/null
+++ b/apps/dashboard/components/virtual-table/types.ts
@@ -0,0 +1,33 @@
+export type Column = {
+ key: string;
+ header: string;
+ headerClassName?: string;
+ width: string;
+ render: (item: T) => React.ReactNode;
+};
+
+export type TableConfig = {
+ rowHeight: number;
+ loadingRows: number;
+ overscan: number;
+ tableBorder: number;
+ throttleDelay: number;
+ headerHeight: number;
+};
+
+export type VirtualTableProps = {
+ data: T[];
+ columns: Column[];
+ isLoading?: boolean;
+ config?: Partial;
+ onRowClick?: (item: T | null) => void;
+ onLoadMore?: () => void;
+ emptyState?: React.ReactNode;
+ keyExtractor: (item: T) => string | number;
+ rowClassName?: (item: T) => string;
+ focusClassName?: (item: T) => string;
+ selectedClassName?: (item: T, isSelected: boolean) => string;
+ selectedItem?: T | null;
+ isFetchingNextPage?: boolean;
+ renderDetails?: (item: T, onClose: () => void, distanceToTop: number) => React.ReactNode;
+};
diff --git a/apps/engineering/content/design/icons.mdx b/apps/engineering/content/design/icons.mdx
index 3647f7a5ad..52ece61868 100644
--- a/apps/engineering/content/design/icons.mdx
+++ b/apps/engineering/content/design/icons.mdx
@@ -2,13 +2,14 @@
title: Icons
description: Available icons for Unkey's apps.
---
+
import { RenderComponentWithSnippet } from "@/app/components/render";
import { Row } from "@/app/components/row";
import { Icon } from "@/app/components/icon-swatch";
import { Icon as XXX } from "@unkey/icons";
-import { TypeTable } from 'fumadocs-ui/components/type-table';
-import { Step, Steps } from 'fumadocs-ui/components/steps';
+import { TypeTable } from "fumadocs-ui/components/type-table";
+import { Step, Steps } from "fumadocs-ui/components/steps";
import {
Bolt,
@@ -30,81 +31,153 @@ import {
Trash,
TriangleWarning,
Ufo,
-} from "@unkey/icons"
-
-
-
+ BarsFilter,
+ Calendar,
+ CircleCarretRight,
+ Magnifier,
+ Refresh3,
+ Sliders,
+ TriangleWarning2,
+ XMark,
+ Grid,
+} from "@unkey/icons";
-
-
-
## Customize
As a rule of thumb, you should only customize the color, but there's always an edge case.
-
+
-`}>
-
-
-
-
-
+`}
+>
+
+
+
+
+
## Icons
These are all the icons available to import.
+
```tsx
-import { IconName } from "@unkey/icons"
+import { IconName } from "@unkey/icons";
```
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
## Adding new icons
Importing icons is a manual process.
+
- Open the icon in the `Nucleo UI Essential` collection and select the icon(s) you want to export.
+ Open the icon in the `Nucleo UI Essential` collection and select the icon(s)
+ you want to export.
- Export it as `jsx`, and remove the title.
- ![Nucleo Export Settings](./icons-export-settings.png)
+ Export it as `jsx`, and remove the title. ![Nucleo Export
+ Settings](./icons-export-settings.png)
@@ -152,5 +225,4 @@ Importing icons is a manual process.
-
diff --git a/internal/icons/src/icons/bars-filter.tsx b/internal/icons/src/icons/bars-filter.tsx
new file mode 100644
index 0000000000..fb86d83acd
--- /dev/null
+++ b/internal/icons/src/icons/bars-filter.tsx
@@ -0,0 +1,55 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const BarsFilter: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/calendar.tsx b/internal/icons/src/icons/calendar.tsx
new file mode 100644
index 0000000000..b30fb157f8
--- /dev/null
+++ b/internal/icons/src/icons/calendar.tsx
@@ -0,0 +1,60 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const Calendar: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/circle-carret-right.tsx b/internal/icons/src/icons/circle-carret-right.tsx
new file mode 100644
index 0000000000..99352564bd
--- /dev/null
+++ b/internal/icons/src/icons/circle-carret-right.tsx
@@ -0,0 +1,37 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const CircleCarretRight: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/grid.tsx b/internal/icons/src/icons/grid.tsx
new file mode 100644
index 0000000000..75fd3d65d2
--- /dev/null
+++ b/internal/icons/src/icons/grid.tsx
@@ -0,0 +1,26 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const Grid: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/magnifier.tsx b/internal/icons/src/icons/magnifier.tsx
new file mode 100644
index 0000000000..88c8e19a6d
--- /dev/null
+++ b/internal/icons/src/icons/magnifier.tsx
@@ -0,0 +1,43 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const Magnifier: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/refresh-3.tsx b/internal/icons/src/icons/refresh-3.tsx
new file mode 100644
index 0000000000..4609742197
--- /dev/null
+++ b/internal/icons/src/icons/refresh-3.tsx
@@ -0,0 +1,38 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const Refresh3: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/sliders.tsx b/internal/icons/src/icons/sliders.tsx
new file mode 100644
index 0000000000..79fdce8c0a
--- /dev/null
+++ b/internal/icons/src/icons/sliders.tsx
@@ -0,0 +1,86 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const Sliders: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/triangle-warning-2.tsx b/internal/icons/src/icons/triangle-warning-2.tsx
new file mode 100644
index 0000000000..d708f9fa80
--- /dev/null
+++ b/internal/icons/src/icons/triangle-warning-2.tsx
@@ -0,0 +1,43 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+
+import type React from "react";
+
+import type { IconProps } from "../props";
+export const TriangleWarning2: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/icons/xmark.tsx b/internal/icons/src/icons/xmark.tsx
new file mode 100644
index 0000000000..70ec35ff94
--- /dev/null
+++ b/internal/icons/src/icons/xmark.tsx
@@ -0,0 +1,42 @@
+/**
+ * Copyright © Nucleo
+ * Version 1.3, January 3, 2024
+ * Nucleo Icons
+ * https://nucleoapp.com/
+ * - Redistribution of icons is prohibited.
+ * - Icons are restricted for use only within the product they are bundled with.
+ *
+ * For more details:
+ * https://nucleoapp.com/license
+ */
+import type React from "react";
+import type { IconProps } from "../props";
+
+export const XMark: React.FC = (props) => {
+ return (
+
+ );
+};
diff --git a/internal/icons/src/index.ts b/internal/icons/src/index.ts
index 08e760840e..51056f8522 100644
--- a/internal/icons/src/index.ts
+++ b/internal/icons/src/index.ts
@@ -17,3 +17,12 @@ export * from "./icons/input-search";
export * from "./icons/shield-key";
export * from "./icons/gauge";
export * from "./icons/gear";
+export * from "./icons/bars-filter";
+export * from "./icons/calendar";
+export * from "./icons/circle-carret-right";
+export * from "./icons/magnifier";
+export * from "./icons/refresh-3";
+export * from "./icons/sliders";
+export * from "./icons/triangle-warning-2";
+export * from "./icons/xmark";
+export * from "./icons/grid";
diff --git a/internal/ui/src/components/button.tsx b/internal/ui/src/components/button.tsx
index a9057ffcee..7f047ca9e9 100644
--- a/internal/ui/src/components/button.tsx
+++ b/internal/ui/src/components/button.tsx
@@ -21,10 +21,10 @@ const buttonVariants = cva(
},
size: {
default: "h-8 px-3 py-1 text-sm",
+ icon: "size-6 p-1",
},
shape: {
square: "size-8 p-1",
- // circle: "",
},
},
defaultVariants: {