diff --git a/packages/kobber-components-poc/src/components/filter/Filter.stories.tsx b/packages/kobber-components-poc/src/components/filter/Filter.stories.tsx new file mode 100644 index 00000000..ecbfe197 --- /dev/null +++ b/packages/kobber-components-poc/src/components/filter/Filter.stories.tsx @@ -0,0 +1,59 @@ +import { + filterApi, + getDisplayCount, +} from "@gyldendal/kobber-components-poc/api/filter"; +import { Filter } from "@gyldendal/kobber-components-poc/react/filter"; +import type { Meta, StoryObj } from "@storybook/react"; +import { reactDecorator } from "../../integrations/storybook/reactDecorator"; + +type Args = Parameters[0] & { + text?: string; + disabled?: boolean; +}; + +const meta: Meta = { + title: "Filter", + decorators: [reactDecorator], + args: { + text: "Filter text", + count: 10, + maxCount: 99, + disabled: false, + selected: false, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const ApiStory: Story = { + render: (args: Args) => { + const api = filterApi(args); + const displayCount = getDisplayCount(args.count, args.maxCount); + return ( + + ); + }, +}; + +export const ReactStory: Story = { + render: (args: Args) => { + return ( + + {args.text} + + ); + }, +}; diff --git a/packages/kobber-components-poc/src/components/filter/filter.css.ts b/packages/kobber-components-poc/src/components/filter/filter.css.ts new file mode 100644 index 00000000..1f18912a --- /dev/null +++ b/packages/kobber-components-poc/src/components/filter/filter.css.ts @@ -0,0 +1,58 @@ +import * as tokens from "@gyldendal/kobber-base/themes/tokens.css-variables.js"; +import { style } from "@vanilla-extract/css"; +import { className } from "../../cssProcessing/className"; + +export const root = style({ + all: "unset", + display: "flex", + height: `var(${tokens.component.filter.size.height})`, + padding: `0 var(${tokens.component.filter.padding.inline})`, + justifyContent: "center", + alignItems: "center", + gap: `var(${tokens.component.filter.gap})`, + borderRadius: `var(${tokens.component.filter.border.radius})`, + backgroundColor: `var(${tokens.component.filter.background.color.fallback})`, + ":hover": { + background: `linear-gradient(0deg, var(${tokens.component.filter.background.color.hover}) 0%, var(${tokens.component.filter.background.color.hover}) 100%), var(${tokens.component.filter.background.color.fallback})`, + }, + ":active": { + background: `var(${tokens.component.filter.background.color.fallback})`, + }, + ":focus-visible": { + outline: "none", + boxShadow: `0 0 0 var(${tokens.universal.focus.border.width}) var(${tokens.universal.focus.border.color})`, + }, + ":disabled": { + cursor: "not-allowed", + opacity: `var(${tokens.universal.disabled.container.opacity})`, + background: `var(${tokens.component.filter.background.color.fallback})`, + }, + selectors: { + '&[aria-disabled="true"], &.disabled': { + opacity: `var(${tokens.universal.disabled.container.opacity})`, + }, + }, +}); + +export const selected = className("selected", { + background: `var(${tokens.component.filter.background.color.active})`, + color: `var(${tokens.universal["text-label"].text.color.brand["tone-b"]})`, + ":hover": { + background: `linear-gradient(0deg, var(${tokens.component.filter.background.color.hover}) 0%, var(${tokens.component.filter.background.color.hover}) 100%), var(${tokens.component.filter.background.color.active})`, + }, + ":active": { + background: `var(${tokens.component.filter.background.color.active})`, + }, +}); + +export const counter = className("counter", { + display: "flex", + color: `var(${tokens.universal["text-label"].text.color.brand["tone-a"]})`, + height: `var(${tokens.component.filter.counter.size.height})`, + minWidth: 24, + padding: `0 var(${tokens.component.filter.counter.padding.inline})`, + justifyContent: "center", + alignItems: "center", + borderRadius: `var(${tokens.component.filter.counter.border.radius})`, + backgroundColor: `var(${tokens.component.filter.counter.background.color.fallback})`, +}); diff --git a/packages/kobber-components-poc/src/components/filter/index.api.ts b/packages/kobber-components-poc/src/components/filter/index.api.ts new file mode 100644 index 00000000..0893a798 --- /dev/null +++ b/packages/kobber-components-poc/src/components/filter/index.api.ts @@ -0,0 +1,39 @@ +import { clsx } from "clsx"; +import type { ApiComponent } from "../../core/api/types"; +import * as classNames from "./filter.css"; + +interface Options { + className?: string; + count?: number; + maxCount?: number; + selected?: boolean; +} + +export const filterApi = ({ className, selected }: Options) => { + return { + root: { + className: clsx({ + [classNames.root]: true, + [classNames.selected]: selected, + [className ?? ""]: Boolean(className), + }), + }, + counter: { + className: clsx({ + [classNames.counter]: true, + }), + }, + } satisfies ApiComponent; +}; + +export const getDisplayCount = (count: Options["count"], maxCount: Options["maxCount"]): string => { + if (count === undefined) { + return "0"; + } + + if (maxCount && count > maxCount) { + return `${maxCount}+`; + } + + return `${count}`; +}; diff --git a/packages/kobber-components-poc/src/components/filter/index.react.tsx b/packages/kobber-components-poc/src/components/filter/index.react.tsx new file mode 100644 index 00000000..2f65e133 --- /dev/null +++ b/packages/kobber-components-poc/src/components/filter/index.react.tsx @@ -0,0 +1,19 @@ +import type { HTMLAttributes, ReactNode } from "react"; +import { filterApi, getDisplayCount } from "./index.api"; + +type Args = Parameters[0]; + +interface Props extends HTMLAttributes, Args { + children?: ReactNode; +} + +export const Filter = ({ children, count, maxCount, ...props }: Props) => { + const { root, counter } = filterApi(props); + const displayCount = getDisplayCount(count, maxCount); + return ( + + ); +};