Skip to content

Commit

Permalink
feat: Overhauled BarList, new prop on BarChart (#1005)
Browse files Browse the repository at this point in the history
* feat: add auto-height to textarea (#798)

Co-authored-by: severinlandolt <sev.landolt@gmail.com>

* fix: Beta dialog (#1000)

* visible class added (#989)
---------

Co-authored-by: Harsh Prajapati <155417349+harshqback@users.noreply.github.com>

* update tooltip (#1001)

* fix: add correct type table (#1003)

* feat: bar category gap (#1002)

* add bar category gap prop

* remove default

* fix: update BarList (#1004)

* fix: hover colors barlist

* fix: sortedData

---------

Co-authored-by: Amir Hesham Ibrahim <amirhesham65@gmail.com>
Co-authored-by: Harsh Prajapati <155417349+harshqback@users.noreply.github.com>
  • Loading branch information
3 people authored Mar 27, 2024
1 parent bd6566b commit 685b0c2
Show file tree
Hide file tree
Showing 12 changed files with 145 additions and 100 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,4 @@ We are always looking for new ideas or other ways to improve Tremor. If you have

[Apache License 2.0](https://github.com/tremorlabs/tremor/blob/main/License)

Copyright &copy; 2024 Tremor. All rights reserved.
Copyright &copy; 2024 Tremor Labs, Inc. All rights reserved.
3 changes: 3 additions & 0 deletions src/components/chart-elements/BarChart/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export interface BarChartProps extends BaseChartProps {
layout?: "vertical" | "horizontal";
stack?: boolean;
relative?: boolean;
barCategoryGap?: string | number;
}

const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) => {
Expand Down Expand Up @@ -91,6 +92,7 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
enableLegendSlider = false,
customTooltip,
rotateLabelX,
barCategoryGap,
tickGap = 5,
className,
...other
Expand Down Expand Up @@ -145,6 +147,7 @@ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>((props, ref) =>
<ResponsiveContainer className="h-full w-full">
{data?.length ? (
<ReChartsBarChart
barCategoryGap={barCategoryGap}
data={data}
stackOffset={stack ? "sign" : relative ? "expand" : "none"}
layout={layout === "vertical" ? "vertical" : "horizontal"}
Expand Down
15 changes: 14 additions & 1 deletion src/components/input-elements/Textarea/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
"use client";
import { getSelectButtonColors, hasValue } from "components/input-elements/selectUtils";
import { useInternalState } from "hooks";

import { makeClassName, mergeRefs, tremorTwMerge } from "lib";
import React, { useRef } from "react";
import React, { useEffect, useRef } from "react";

export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
defaultValue?: string | number;
value?: string | number;
error?: boolean;
errorMessage?: string;
disabled?: boolean;
autoHeight?: boolean;
onValueChange?: (value: any) => void;
}

Expand All @@ -26,6 +28,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>((props, re
className,
onChange,
onValueChange,
autoHeight = false,
...other
} = props;
const [val, setVal] = useInternalState(defaultValue, value);
Expand All @@ -34,6 +37,16 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>((props, re

const hasSelection = hasValue(val);

useEffect(() => {
const textAreaHTMLRef = inputRef.current;
if (autoHeight && textAreaHTMLRef) {
textAreaHTMLRef.style.height = "60px";
// Calculates the height dynamically
const scrollHeight = textAreaHTMLRef.scrollHeight;
textAreaHTMLRef.style.height = scrollHeight + "px";
}
}, [autoHeight, inputRef, val]);

return (
<>
<textarea
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout-elements/Dialog/DialogPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const DialogPanel = React.forwardRef<HTMLDivElement, DialogPanelProps>((props, r
className={tremorTwMerge(
makeDisplayClassName("panel"),
// common
"w-full max-w-lg overflow-hidden text-left ring-1 shadow-tremor transition-all transform",
"w-full max-w-lg overflow-visible text-left ring-1 shadow-tremor transition-all transform",
// light
"bg-tremor-background text-tremor-content ring-tremor-ring",
// dark
Expand Down
2 changes: 1 addition & 1 deletion src/components/list-elements/Table/TableHeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const makeTableHeaderCellClassName = makeClassName("TableHeaderCell");

const TableHeaderCell = React.forwardRef<
HTMLTableCellElement,
React.HTMLAttributes<HTMLTableCellElement>
React.ThHTMLAttributes<HTMLTableCellElement>
>((props, ref) => {
const { children, className, ...other } = props;
return (
Expand Down
4 changes: 2 additions & 2 deletions src/components/util-elements/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ const Tooltip = ({ text, open, x, y, refs, strategy, getFloatingProps }: Tooltip
<div
className={tremorTwMerge(
// common
"max-w-xs text-sm z-20 rounded-tremor-default opacity-100 px-2.5 py-1 whitespace-nowrap",
"max-w-xs text-sm z-20 rounded-tremor-default opacity-100 px-2.5 py-1",
// light
"text-white bg-tremor-background-emphasis",
// dark
"text-white dark:bg-dark-tremor-background-subtle",
"dark:text-tremor-content-emphasis dark:bg-white",
)}
ref={refs.setFloating}
style={{
Expand Down
197 changes: 106 additions & 91 deletions src/components/vis-elements/BarList/BarList.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React from "react";
import {
Color,
Expand All @@ -21,24 +23,13 @@ type Bar<T> = T & {
color?: Color;
};

const getWidthsFromValues = (dataValues: number[]) => {
let maxValue = -Infinity;
dataValues.forEach((value) => {
maxValue = Math.max(maxValue, value);
});

return dataValues.map((value) => {
if (value === 0) return 0;
return Math.max((value / maxValue) * 100, 1);
});
};

export interface BarListProps<T = any> extends React.HTMLAttributes<HTMLDivElement> {
data: Bar<T>[];
valueFormatter?: ValueFormatter;
color?: Color;
showAnimation?: boolean;
onValueChange?: (payload: Bar<T>) => void;
sortOrder?: "ascending" | "descending";
}

function BarListInner<T>(props: BarListProps<T>, ref: React.ForwardedRef<HTMLDivElement>) {
Expand All @@ -48,13 +39,29 @@ function BarListInner<T>(props: BarListProps<T>, ref: React.ForwardedRef<HTMLDiv
valueFormatter = defaultValueFormatter,
showAnimation = false,
onValueChange,
sortOrder = "descending",
className,
...other
} = props;

const widths = getWidthsFromValues(data.map((item) => item.value));
const Component = onValueChange ? "button" : "div";
const sortedData = React.useMemo(() => {
if (sortOrder) {
return [...data].sort((a, b) => {
return sortOrder === "ascending" ? a.value - b.value : b.value - a.value;
});
}
return data;
}, [data, sortOrder]);

const rowHeight = "h-9";
const widths = React.useMemo(() => {
const maxValue = Math.max(...sortedData.map((item) => item.value), 0);
return sortedData.map((item) =>
item.value === 0 ? 0 : Math.max((item.value / maxValue) * 100, 2),
);
}, [sortedData]);

const rowHeight = "h-8";

return (
<div
Expand All @@ -64,114 +71,122 @@ function BarListInner<T>(props: BarListProps<T>, ref: React.ForwardedRef<HTMLDiv
"flex justify-between space-x-6",
className,
)}
aria-sort={sortOrder}
{...other}
>
<div className={tremorTwMerge(makeBarListClassName("bars"), "relative w-full")}>
{data.map((item, idx) => {
<div className={tremorTwMerge(makeBarListClassName("bars"), "relative w-full space-y-1.5")}>
{sortedData.map((item, index) => {
const Icon = item.icon;

return (
<div
<Component
key={item.key ?? item.name}
onClick={() => {
onValueChange?.(item);
}}
className={tremorTwMerge(
makeBarListClassName("bar"),
// common
"flex items-center rounded-tremor-small bg-opacity-30",
rowHeight,
item.color || color
? getColorClassNames(item.color ?? (color as Color), colorPalette.background)
.bgColor
: "bg-tremor-brand-subtle dark:bg-dark-tremor-brand-subtle dark:bg-opacity-30",
idx === data.length - 1 ? "mb-0" : "mb-2",
"group w-full flex items-center rounded-tremor-small",
onValueChange
? [
"cursor-pointer",
// hover
"hover:bg-tremor-background-muted dark:hover:bg-dark-tremor-background-subtle/40",
]
: "",
)}
style={{
width: `${widths[idx]}%`,
transition: showAnimation ? "all 1s" : "",
}}
>
<div
className={tremorTwMerge(
"absolute max-w-full flex left-2",
onValueChange ? "cursor-pointer" : "",
"flex items-center rounded transition-all bg-opacity-40",
rowHeight,
item.color || color
? [
getColorClassNames(item.color ?? (color as Color), colorPalette.background)
.bgColor,
onValueChange ? "group-hover:bg-opacity-30" : "",
]
: "bg-tremor-brand-subtle dark:bg-dark-tremor-brand-subtle/60",
onValueChange && !(item.color || color)
? "group-hover:bg-tremor-brand-subtle/30 group-hover:dark:bg-dark-tremor-brand-subtle/70"
: "",
// margin and duration
index === sortedData.length - 1 ? "mb-0" : "",
showAnimation ? "duration-500" : "",
)}
onClick={() => {
onValueChange?.(item);
}}
style={{ width: `${widths[index]}%`, transition: showAnimation ? "all 1s" : "" }}
>
{Icon ? (
<Icon
className={tremorTwMerge(
makeBarListClassName("barIcon"),
// common
"flex-none h-5 w-5 mr-2",
// light
"text-tremor-content",
// dark
"dark:text-dark-tremor-content",
)}
/>
) : null}
{item.href ? (
<a
href={item.href}
target={item.target ?? "_blank"}
rel="noreferrer"
className={tremorTwMerge(
makeBarListClassName("barLink"),
// common
"whitespace-nowrap hover:underline truncate text-tremor-default",
onValueChange ? "cursor-pointer" : "",
// light
"text-tremor-content-emphasis",
// dark
"dark:text-dark-tremor-content-emphasis",
)}
onClick={() => {
onValueChange?.(item);
}}
>
{item.name}
</a>
) : (
<p
className={tremorTwMerge(
makeBarListClassName("barText"),
// common
"whitespace-nowrap truncate text-tremor-default",
onValueChange ? "cursor-pointer" : "",
// light
"text-tremor-content-emphasis",
// dark
"dark:text-dark-tremor-content-emphasis",
)}
onClick={() => {
onValueChange?.(item);
}}
>
{item.name}
</p>
)}
<div className={tremorTwMerge("absolute left-2 pr-4 flex max-w-full")}>
{Icon ? (
<Icon
className={tremorTwMerge(
makeBarListClassName("barIcon"),
// common
"flex-none h-5 w-5 mr-2",
// light
"text-tremor-content",
// dark
"dark:text-dark-tremor-content",
)}
/>
) : null}
{item.href ? (
<a
href={item.href}
target={item.target ?? "_blank"}
rel="noreferrer"
className={tremorTwMerge(
makeBarListClassName("barLink"),
// common
"whitespace-nowrap hover:underline truncate text-tremor-default",
onValueChange ? "cursor-pointer" : "",
// light
"text-tremor-content-emphasis",
// dark
"dark:text-dark-tremor-content-emphasis",
)}
onClick={(event) => event.stopPropagation()}
>
{item.name}
</a>
) : (
<p
className={tremorTwMerge(
makeBarListClassName("barText"),
// common
"whitespace-nowrap truncate text-tremor-default",
// light
"text-tremor-content-emphasis",
// dark
"dark:text-dark-tremor-content-emphasis",
)}
>
{item.name}
</p>
)}
</div>
</div>
</div>
</Component>
);
})}
</div>
<div className={(makeBarListClassName("labels"), "text-right min-w-min")}>
{data.map((item, idx) => (
<div className={makeBarListClassName("labels")}>
{sortedData.map((item, index) => (
<div
key={item.key ?? item.name}
className={tremorTwMerge(
makeBarListClassName("labelWrapper"),
"flex justify-end items-center",
rowHeight,
idx === data.length - 1 ? "mb-0" : "mb-2",
index === sortedData.length - 1 ? "mb-0" : "mb-1.5",
)}
>
<p
className={tremorTwMerge(
makeBarListClassName("labelText"),
// common
"whitespace-nowrap truncate text-tremor-default",
"whitespace-nowrap leading-none truncate text-tremor-default",
// light
"text-tremor-content-emphasis",
// dark
Expand Down
7 changes: 7 additions & 0 deletions src/stories/chart-elements/BarChart.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,10 @@ export const tickGap: Story = {
tickGap: 200,
},
};

export const barCategoryGap: Story = {
args: {
data: data,
barCategoryGap: "20%",
},
};
6 changes: 6 additions & 0 deletions src/stories/input-elements/TextArea.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ export const DefaultValue: Story = {
export const ControlledDefault: Story = {
...ControlledTemplate,
};

export const AutoHeight: Story = {
args: {
autoHeight: true,
},
};
2 changes: 1 addition & 1 deletion src/stories/list-elements/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const Default: Story = {
<Table {...args}>
<TableHead>
<TableRow>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell colSpan={3}>Name</TableHeaderCell>
<TableHeaderCell>Sales ($)</TableHeaderCell>
<TableHeaderCell>Region</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
Expand Down
Loading

0 comments on commit 685b0c2

Please sign in to comment.