Skip to content

Commit

Permalink
Rewrite select (#137)
Browse files Browse the repository at this point in the history
* Add select changes

* Add Select Changes

* Remove drag feature

* Fix Select changes

* Fix tests

* Fix sort issue

* Fix package install

* Remove yarn lock

* Fix initial loading of the item

* Fix showSearch in storybook

* Update storybook orientation

* Call onSelect on value change

* Add Select Changes

* Fix Hidden Select Component

* Fix linting issues

* Fix show Search in Select

* Fix Select Open State issue

* Add Select Changes

* Add Select changes

* Add Select Changes

* Fix Multi select sorting

* Remove open from story book

* Fix initial selection and also props naming

* Add styling changes

* Change onChange to onSelect in Select and MultiSelect props

* Fix Select Test

* Add types

* Fix incorrect assignment

* Fix Select Styling

* Add Ellipsis changes
  • Loading branch information
vineethasok authored Sep 26, 2023
1 parent a13fd34 commit b3fa8d6
Show file tree
Hide file tree
Showing 27 changed files with 2,208 additions and 1,249 deletions.
509 changes: 70 additions & 439 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-tooltip": "^1.0.6",
"cmdk": "^0.2.0",
"lodash": "^4.17.21",
"react-syntax-highlighter": "^15.5.0"
"react-sortablejs": "^6.1.4",
"react-syntax-highlighter": "^15.5.0",
"sortablejs": "^1.15.0"
},
"devDependencies": {
"@radix-ui/react-switch": "^1.0.2",
Expand All @@ -81,6 +82,7 @@
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-syntax-highlighter": "^15.5.7",
"@types/sortablejs": "^1.15.2",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
Expand Down
62 changes: 51 additions & 11 deletions src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import styled from "styled-components";
import { Icon } from "@/components";
import { HorizontalDirection, Icon, IconName } from "@/components";
import { HTMLAttributes, MouseEvent, ReactNode } from "react";
import { EllipsisContainer } from "../commonElement";
export type BadgeState =
| "default"
| "success"
Expand All @@ -11,24 +13,26 @@ export type BadgeState =

export type BadgeSize = "sm" | "md";

export interface BadgeProps {
text: string;
export interface CommonBadgeProps extends HTMLAttributes<HTMLDivElement> {
text: ReactNode;
state?: BadgeState;
size?: BadgeSize;
icon?: IconName;
iconDir?: HorizontalDirection;
}

export interface DismissibleBadge extends BadgeProps {
export interface DismissibleBadge extends CommonBadgeProps {
dismissible: true;
onClose: () => void;
onClose: (e: MouseEvent<HTMLOrSVGElement>) => void;
}

export interface NonDismissibleBadge extends BadgeProps {
export interface NonDismissibleBadge extends CommonBadgeProps {
dismissible?: never;
onClose?: never;
}

const Wrapper = styled.div<{ $state?: BadgeState; $size?: BadgeSize }>`
display: inline-block;
display: inline-flex;
${({ $state = "default", $size = "md", theme }) => `
background-color: ${theme.click.badge.color.background[$state]};
color: ${theme.click.badge.color.text[$state]};
Expand All @@ -45,29 +49,65 @@ const Content = styled.div<{ $state?: BadgeState; $size?: BadgeSize }>`
gap: ${({ $size = "md", theme }) => theme.click.badge.space[$size].gap};
`;

const CrossContainer = styled.svg<{ $state?: BadgeState; $size?: BadgeSize }>`
const SvgContainer = styled.svg<{ $state?: BadgeState; $size?: BadgeSize }>`
${({ $state = "default", $size = "md", theme }) => `
color: ${theme.click.badge.color.text[$state]};
height: ${theme.click.badge.icon[$size].size.height};
width: ${theme.click.badge.icon[$size].size.width};
`}
`;
const BadgeContent = styled(EllipsisContainer)<{
$state?: BadgeState;
$size?: BadgeSize;
}>`
svg {
${({ $state = "default", $size = "md", theme }) => `
color: ${theme.click.badge.color.text[$state]};
height: ${theme.click.badge.icon[$size].size.height};
width: ${theme.click.badge.icon[$size].size.width};
gap: inherit;
`}
}
`;

export type BadgeProps = NonDismissibleBadge | DismissibleBadge;

export const Badge = ({
icon,
iconDir,
text,
state = "default",
size,
dismissible,
onClose,
}: NonDismissibleBadge | DismissibleBadge) => (
...props
}: BadgeProps) => (
<Wrapper
$state={state}
$size={size}
{...props}
>
<Content>
{text}
{icon && iconDir === "start" && (
<SvgContainer
as={Icon}
name={icon}
$state={state}
$size={size}
/>
)}
<BadgeContent>{text}</BadgeContent>
{icon && iconDir === "end" && (
<SvgContainer
as={Icon}
name={icon}
$state={state}
$size={size}
/>
)}

{dismissible && (
<CrossContainer
<SvgContainer
name="cross"
$state={state}
as={Icon}
Expand Down
36 changes: 36 additions & 0 deletions src/components/EllipsisContent/EllipsisContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { HTMLAttributes, forwardRef } from "react";
import { mergeRefs } from "@/utils/mergeRefs";
import styled from "styled-components";

const EllipsisContainer = styled.div`
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
justify-content: flex-start;
width: fill-available;
flex: 1;
& > *:not(button) {
overflow: hidden;
text-overflow: ellipsis;
}
`;

export const EllipsisContent = forwardRef<HTMLElement, HTMLAttributes<HTMLSpanElement>>(
(props, ref) => {
return (
<EllipsisContainer
ref={mergeRefs([
ref,
node => {
console.log({ a: node?.scrollWidth, b: node?.clientWidth });
if (node && node.scrollWidth > node.clientWidth) {
node.title = node.innerText;
}
},
])}
{...props}
/>
);
}
);
5 changes: 4 additions & 1 deletion src/components/GenericMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const GenericMenuItem = styled.div`
color:${theme.click.genericMenu.item.color.text.hover};
cursor: pointer;
}
&[data-state="open"] {
&[data-state="open"], &[data-state-"checked"] {
background:${theme.click.genericMenu.item.color.background.active};
color:${theme.click.genericMenu.item.color.text.active};
font: ${theme.click.genericMenu.item.typography.label.active};
Expand All @@ -114,4 +114,7 @@ export const GenericMenuItem = styled.div`
&[data-state="open"] .dropdown-arrow {
left: 0.5rem;
}
&[hidden] {
display: none;
}
`;
55 changes: 55 additions & 0 deletions src/components/IconWrapper/IconWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ReactNode } from "react";
import { HorizontalDirection, Icon, IconName } from "@/components";
import { IconSize } from "@/components/Icon/types";
import styled from "styled-components";
import { EllipsisContent } from "../EllipsisContent/EllipsisContent";

const LabelContainer = styled.div`
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
width: -webkit-fill-available;
width: fill-available;
width: stretch;
gap: ${({ theme }) => theme.click.sidebar.navigation.item.default.space.gap};
`;

const IconWrapper = ({
icon,
iconDir = "start",
size = "sm",
width,
height,
children,
}: {
icon?: IconName;
iconDir?: HorizontalDirection;
children: ReactNode;
size?: IconSize;
width?: number | string;
height?: number | string;
}) => {
return (
<LabelContainer>
{icon && iconDir === "start" && (
<Icon
name={icon}
size={size}
width={width}
height={height}
/>
)}
<EllipsisContent>{children}</EllipsisContent>
{icon && iconDir === "end" && (
<Icon
name={icon}
size={size}
width={width}
height={height}
/>
)}
</LabelContainer>
);
};
export default IconWrapper;
142 changes: 142 additions & 0 deletions src/components/Select/MultiSelect.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { MultiSelect, MultiSelectProps } from "./MultiSelect";
import { selectOptions } from "./selectOptions";
import { useEffect, useState } from "react";
interface Props extends Omit<MultiSelectProps, "value"> {
clickableNoData?: boolean;
value: string;
childrenType: "children" | "options";
}
const MultiSelectExample = ({
clickableNoData,
childrenType,
value,
...props
}: Props) => {
const [selectedValues, setSelectedValues] = useState(
value ? value.split(",") : undefined
);
useEffect(() => {
setSelectedValues(value ? value.split(",") : undefined);
}, [value]);

if (childrenType === "options") {
return (
<MultiSelect
value={selectedValues}
onCreateOption={
clickableNoData ? search => console.log("Clicked ", search) : undefined
}
options={selectOptions}
onSelect={value => setSelectedValues(value)}
{...props}
/>
);
}
return (
<MultiSelect
value={value ? value.split(",") : undefined}
onCreateOption={
clickableNoData ? search => console.log("Clicked ", search) : undefined
}
{...props}
>
<MultiSelect.Group heading="Group label">
<MultiSelect.Item
value="content0"
icon="user"
>
Content0
</MultiSelect.Item>
</MultiSelect.Group>
<div>
<MultiSelect.Item value="content1">Content1 long text content</MultiSelect.Item>
</div>
<MultiSelect.Item
value="content2"
label="Content2"
/>
<MultiSelect.Item value="content3">Content3</MultiSelect.Item>
</MultiSelect>
);
};
export default {
component: MultiSelectExample,
title: "Forms/MultiSelect",
tags: ["form-field", "select", "autodocs"],
argTypes: {
label: { control: "text" },
disabled: { control: "boolean" },
sortable: { control: "boolean" },
error: { control: "text" },
value: { control: "text" },
defaultValue: { control: "text" },
name: { control: "text" },
required: { control: "boolean" },
showSearch: { control: "boolean" },
form: { control: "text" },
clickableNoData: { control: "boolean" },
showCheck: { control: "boolean" },
orientation: { control: "inline-radio", options: ["horizontal", "vertical"] },
dir: { control: "inline-radio", options: ["start", "end"] },
},
};

export const Playground = {
args: {
label: "Label",
value: "content1",
showSearch: true,
childrenType: "children",
},
parameters: {
docs: {
source: {
transform: (_: string, story: { args: Props; [x: string]: unknown }) => {
const { clickableNoData, childrenType, value, ...props } = story.args;
return `<MultiSelect\n value={${JSON.stringify((value ?? "").split(","))}}\n${
clickableNoData
? " onCreateOption={search => console.log('Clicked ', search)}\n"
: ""
}
${Object.entries(props)
.flatMap(([key, value]) =>
typeof value === "boolean"
? value
? ` ${key}`
: []
: ` ${key}=${typeof value == "string" ? `"${value}"` : `{${value}}`}`
)
.join("\n")}
${
childrenType === "options"
? `options={${JSON.stringify(selectOptions, null, 2)}}\n/`
: ""
}>
${
childrenType !== "options"
? `
<MultiSelect.Group heading="Group label">
<MultiSelect.Item value="content0">
<Icon name="user" />
Content0
</MultiSelect.Item>
</MultiSelect.Group>
<div>
<MultiSelect.Item value="content1">Content1 long text content</MultiSelect.Item>
</div>
<MultiSelect.Item
value="content2"
disabled
>
Content2
</MultiSelect.Item>
<MultiSelect.Item value="content3">Content3</MultiSelect.Item>
</Select>
`
: ""
}`;
},
},
},
},
};
Loading

1 comment on commit b3fa8d6

@vercel
Copy link

@vercel vercel bot commented on b3fa8d6 Sep 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

click-ui – ./

click-ui-git-main-clickhouse.vercel.app
click-ui.vercel.app
click-ui-clickhouse.vercel.app

Please sign in to comment.