Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/sixty-keys-enjoy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@vygruppen/spor-react": patch
---

Add InputChip component, and style adjustments to Select
33 changes: 33 additions & 0 deletions packages/spor-react/src/input/InputChip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Box, Tag as ChakraTag, TagRootProps } from "@chakra-ui/react";
import { IconComponent } from "@vygruppen/spor-icon-react";
import React from "react";

export type InputChipProps = TagRootProps & {
startIcon?: IconComponent;
endIcon?: IconComponent;
onClick?: VoidFunction;
};

export const InputChip = ({
startIcon,
endIcon,
children,
ref,
...rest
}: InputChipProps & { ref?: React.Ref<HTMLDivElement> }) => {
return (
<ChakraTag.Root ref={ref} {...rest} as="button">
{startIcon && (
<ChakraTag.StartElement>
<Box as={startIcon} />
</ChakraTag.StartElement>
)}
<ChakraTag.Label>{children}</ChakraTag.Label>
{endIcon && (
<ChakraTag.EndElement>
<Box as={endIcon} />
</ChakraTag.EndElement>
)}
</ChakraTag.Root>
);
};
51 changes: 44 additions & 7 deletions packages/spor-react/src/input/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type {
} from "@chakra-ui/react";
import {
Box,
Checkbox as ChakraCheckbox,
Flex,
Portal,
Select as ChakraSelect,
useSelectContext,
Expand All @@ -21,6 +23,7 @@ import * as React from "react";

import { CloseButton } from "@/button";

import { Badge } from "..";
import { Field, FieldProps } from "./Field";

export type SelectProps = ChakraSelectRootProps &
Expand Down Expand Up @@ -95,7 +98,7 @@ export const Select = ({
position="relative"
>
<SelectTrigger data-attachable>
<SelectValueText withPlaceholder={label ? true : false} />
<SelectValueText withPlaceholder={!!label} />
</SelectTrigger>
{label && <SelectLabel css={styles.label}>{label}</SelectLabel>}
<SelectContent css={styles.selectContent} baseStyle={css}>
Expand All @@ -112,7 +115,7 @@ export const SelectLabel = (props: SelectLabelProps) => {
return (
<ChakraSelect.Label
{...props}
data-selected={value.length > 0 ? true : undefined}
data-selected={value.length > 0 || undefined}
/>
);
};
Expand All @@ -131,8 +134,19 @@ export const SelectItem = ({
const { item, children, description, ...rest } = props;
const recipe = useSlotRecipe({ key: "select" });
const styles = recipe();
const selectContext = useSelectContext();
const multiple = selectContext.multiple;
const isSelected = selectContext.value.includes(item.value);

return (
<ChakraSelect.Item item={item} {...rest} ref={ref} css={styles.item}>
{multiple && (
<ChakraCheckbox.Root checked={isSelected} pointerEvents="none">
<ChakraCheckbox.Control>
<ChakraCheckbox.Indicator />
</ChakraCheckbox.Control>
</ChakraCheckbox.Root>
)}
<Box width="100%">
<ChakraSelect.ItemText display="flex">{children}</ChakraSelect.ItemText>
{description && (
Expand All @@ -142,12 +156,15 @@ export const SelectItem = ({
)}
</Box>

<ChakraSelect.ItemIndicator>
<CheckmarkFill18Icon />
</ChakraSelect.ItemIndicator>
{!multiple && (
<ChakraSelect.ItemIndicator>
<CheckmarkFill18Icon />
</ChakraSelect.ItemIndicator>
)}
</ChakraSelect.Item>
);
};
SelectItem.displayName = "SelectItem";

type SelectItemGroupProps = ChakraSelect.ItemGroupProps & {
label: React.ReactNode;
Expand Down Expand Up @@ -249,6 +266,7 @@ type SelectValueTextProps = Omit<ChakraSelect.ValueTextProps, "children"> & {
children?(items: CollectionItem[]): React.ReactNode;
placeholder?: string;
withPlaceholder?: boolean;
multiple?: boolean;
};

export const SelectValueText = function SelectValueText({
Expand All @@ -258,6 +276,10 @@ export const SelectValueText = function SelectValueText({
ref?: React.Ref<HTMLSpanElement>;
}) {
const { children, withPlaceholder, placeholder, ...rest } = props;

const selectContext = useSelectContext();
const multiple = selectContext.multiple;

return (
<ChakraSelect.ValueText
{...rest}
Expand All @@ -277,9 +299,24 @@ export const SelectValueText = function SelectValueText({
const items = select.selectedItems;
if (items.length === 0) return placeholder;
if (children) return children(items);
if (items.length === 1)
if (multiple) {
return (
<Flex gap={0.5} marginBottom={1}>
{items.map((item) => (
<Badge
key={select.collection.stringifyItem(item)}
size="sm"
colorPalette="green"
>
{select.collection.stringifyItem(item)}
</Badge>
))}
</Flex>
);
}
if (items.length === 1) {
return select.collection.stringifyItem(items[0]);
return `${items.length} selected`;
}
}}
</ChakraSelect.Context>
</ChakraSelect.ValueText>
Expand Down
1 change: 1 addition & 0 deletions packages/spor-react/src/input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from "./Combobox";
export * from "./Field";
export * from "./Fieldset";
export * from "./Input";
export * from "./InputChip";
export * from "./ListBox";
export * from "./Menu";
export * from "./NativeSelect";
Expand Down
2 changes: 2 additions & 0 deletions packages/spor-react/src/theme/slot-recipes/anatomy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ export const comboboxAnatomy = arkComboboxAnatomy.extendWith(
"empty",
);

export const tagAnatomy = createAnatomy("tag").parts("root");

export const menuAnatomy = createAnatomy("menu").parts(
"trigger",
"content",
Expand Down
2 changes: 2 additions & 0 deletions packages/spor-react/src/theme/slot-recipes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { drawerSlotRecipe } from "./drawer";
import { fieldSlotRecipe } from "./field";
import { floatingActionButtonSlotRecipe } from "./floating-action-button";
import { infoTagSlotRecipe } from "./info-tag";
import { inputChipSlotRecipe } from "./input-chip";
import { lineIconSlotRecipe } from "./line-icon";
import { listSlotRecipe } from "./list";
import { listBoxSlotRecipe } from "./listbox";
Expand Down Expand Up @@ -70,5 +71,6 @@ export const slotRecipes = {
checkboxCard: choiceChipSlotRecipe,
collapsible: collapsibleSlotRecipe,
tooltip: popoverSlotRecipe,
tag: inputChipSlotRecipe,
menu: menuSlotRecipe,
};
118 changes: 118 additions & 0 deletions packages/spor-react/src/theme/slot-recipes/input-chip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { defineSlotRecipe } from "@chakra-ui/react";

import { tagAnatomy } from "./anatomy";

export const inputChipSlotRecipe = defineSlotRecipe({
slots: tagAnatomy.keys(),
className: "chakra-tag",
base: {
root: {
display: "flex",
direction: "row",
width: "fit-content",
height: "fit-content",
alignItems: "center",
justifyContent: "center",
gap: "1",
outline: "none",
"&:focus": {
outline: "2px solid",
outlineColor: "outline.focus",
},
},
},
variants: {
variant: {
core: {
root: {
backgroundColor: "surface",
border: "1px solid",
borderColor: "outline",
"&:hover": {
outline: "2px solid",
outlineColor: "core.outline.hover",
},
"&:active": {
outline: "none",
backgroundColor: "core.surface.active",
},
},
},
accent: {
root: {
backgroundColor: "accent.surface",
color: "text.highlight",
"& svg": {
color: "icon.highlight",
},
"&:hover": {
backgroundColor: "accent.surface.hover",
},
"&:active": {
backgroundColor: "accent.surface.active",
outline: "none",
},
},
},
brand: {
root: {
backgroundColor: "brand.surface",
color: "text.inverted",
"& svg": {
color: "icon.inverted",
},
"&:hover": {
backgroundColor: "brand.surface.hover",
},
"&:active": {
backgroundColor: "brand.surface.active",
outline: "none",
},
},
},
},
size: {
xs: {
root: {
fontSize: "desktop.xs",
paddingX: "1.5",
paddingY: "0",
fontWeight: "normal",
borderRadius: "xs",
},
},
sm: {
root: {
fontSize: "desktop.sm",
paddingX: "2",
paddingY: "0.5",
fontWeight: "bold",
borderRadius: "9px",
},
},
md: {
root: {
padding: 5,
fontSize: "desktop.md",
paddingX: "2",
paddingY: "1",
fontWeight: "bold",
borderRadius: "sm",
},
},
lg: {
root: {
fontSize: "desktop.md",
paddingX: "2",
paddingY: "3",
fontWeight: "bold",
borderRadius: "md",
},
},
},
},
defaultVariants: {
variant: "core",
size: "sm",
},
});
8 changes: 4 additions & 4 deletions packages/spor-react/src/theme/slot-recipes/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,28 @@ export const selectSlotRecipe = defineSlotRecipe({
outlineOffset: "2px",
outline: "2px solid",
outlineColor: "outline.focus",
backgroundColor: "ghost.surface.hover",
backgroundColor: "accent.surface.hover",
},
"&[data-highlighted]": {
outlineOffset: "2px",
outline: "2px solid",
outlineColor: "outline.focus",
},
_active: {
backgroundColor: "ghost.surface.active",
backgroundColor: "accent.surface.active",
},
_highlighted: {
_active: {
color: "text",
},
},
_hover: {
backgroundColor: "ghost.surface.hover",
backgroundColor: "accent.surface.hover",
outline: "2px solid core.outline",
outlineOffset: "2px",
},
_selected: {
backgroundColor: "ghost.surface.active",
backgroundColor: "accent.surface",
},
_icon: {
width: 3,
Expand Down
Loading