diff --git a/.changeset/violet-paws-begin.md b/.changeset/violet-paws-begin.md new file mode 100644 index 0000000000..f3809a2f01 --- /dev/null +++ b/.changeset/violet-paws-begin.md @@ -0,0 +1,6 @@ +--- +"@digdir/designsystemet-css": patch +"@digdir/designsystemet-react": patch +--- + +Badge: Convert to two elements, add `Badge.Position` component for placement diff --git a/packages/css/src/badge.css b/packages/css/src/badge.css index a6fadeda03..9dd1a48dd9 100644 --- a/packages/css/src/badge.css +++ b/packages/css/src/badge.css @@ -1,91 +1,93 @@ .ds-badge { - /* color vars */ --dsc-badge-background: var(--ds-color-base-default); --dsc-badge-color: var(--ds-color-contrast-default); - - /* What do we do here? */ - - &[data-color='neutral'] { - --dsc-badge-background: var(--ds-color-neutral-surface-default); - --dsc-badge-color: var(--ds-color-neutral-text-default); - } - - /* sizing vars */ --dsc-badge-padding: 0 calc(var(--ds-spacing-1) + calc(var(--ds-spacing-1) / 2)); --dsc-badge-size: calc(var(--ds-sizing-3) + calc(var(--ds-spacing-1) / 2)); - box-sizing: border-box; - display: inline-flex; - line-height: var(--ds-line-height-sm); - position: relative; - &::before { content: attr(data-count); background: var(--dsc-badge-background); border-radius: var(--ds-border-radius-full); box-sizing: border-box; color: var(--dsc-badge-color); - display: grid; + display: inline-grid; font-size: var(--ds-font-size-minus-1); /* Default to small when size is not defined */ min-height: var(--dsc-badge-size); min-width: var(--dsc-badge-size); padding: var(--dsc-badge-padding); place-items: center; + box-sizing: border-box; + line-height: var(--ds-line-height-sm); + width: fit-content; } - & :where(img, svg) { - flex-shrink: 0; /* Never shrink icon */ - font-size: 1.25em; /* Auto scale icon based on font-size */ + /* What do we do here? */ + + &[data-color='neutral'] { + --dsc-badge-background: var(--ds-color-neutral-surface-default); + --dsc-badge-color: var(--ds-color-neutral-text-default); } +} - /* If placement is present, we are floating */ - &[data-placement]::before { +.ds-badge--position { + box-sizing: border-box; + display: inline-flex; + position: relative; + height: fit-content; + width: fit-content; + + & .ds-badge::before { position: absolute; } - &[data-placement='top-right']::before { + & :where(img, svg) { + flex-shrink: 0; /* Never shrink icon */ + font-size: 1.25em; /* Auto scale icon based on font-size */ + } + + &[data-placement='top-right'] .ds-badge::before { top: 0; right: 0; translate: 50% -50%; } - &[data-placement='top-left']::before { + &[data-placement='top-left'] .ds-badge::before { top: 0; left: 0; translate: -50% -50%; } - &[data-placement='bottom-right']::before { + &[data-placement='bottom-right'] .ds-badge::before { bottom: 0; right: 0; translate: 50% 50%; } - &[data-placement='bottom-left']::before { + &[data-placement='bottom-left'] .ds-badge::before { bottom: 0; left: 0; translate: -50% 50%; } - &[data-placement='top-right'][data-overlap='circle']::before { + &[data-placement='top-right'][data-overlap='circle'] .ds-badge::before { top: 14%; right: 14%; translate: 50% -50%; } - &[data-placement='top-left'][data-overlap='circle']::before { + &[data-placement='top-left'][data-overlap='circle'] .ds-badge::before { top: 14%; left: 14%; translate: -50% -50%; } - &[data-placement='bottom-right'][data-overlap='circle']::before { + &[data-placement='bottom-right'][data-overlap='circle'] .ds-badge::before { bottom: 14%; right: 14%; translate: 50% 50%; } - &[data-placement='bottom-left'][data-overlap='circle']::before { + &[data-placement='bottom-left'][data-overlap='circle'] .ds-badge::before { bottom: 14%; left: 14%; translate: -50% 50%; diff --git a/packages/react/src/components/Avatar/Avatar.stories.tsx b/packages/react/src/components/Avatar/Avatar.stories.tsx index 9899e3e5b1..5cd3b16aef 100644 --- a/packages/react/src/components/Avatar/Avatar.stories.tsx +++ b/packages/react/src/components/Avatar/Avatar.stories.tsx @@ -90,11 +90,12 @@ export const InDropdown: Story = () => ( - + + ON - + Ola Nordmann diff --git a/packages/react/src/components/Badge/Badge.mdx b/packages/react/src/components/Badge/Badge.mdx index 1aff5757a6..02599fb874 100644 --- a/packages/react/src/components/Badge/Badge.mdx +++ b/packages/react/src/components/Badge/Badge.mdx @@ -18,17 +18,13 @@ import * as BadgeStories from './Badge.stories'; import { Badge } from '@digdir/designsystemet-react'; /* Inline */ - + /* Flytende over element */ - + + - - -/* Flytende over element som dot */ - - - + ``` ## Flytende over element diff --git a/packages/react/src/components/Badge/Badge.stories.tsx b/packages/react/src/components/Badge/Badge.stories.tsx index ac2533dd60..d8cca5f566 100644 --- a/packages/react/src/components/Badge/Badge.stories.tsx +++ b/packages/react/src/components/Badge/Badge.stories.tsx @@ -11,7 +11,7 @@ import { } from '@navikt/aksel-icons'; import { Button } from '../Button'; import { Tabs } from '../Tabs'; -import { Badge } from './Badge'; +import { Badge } from './'; type Story = StoryFn; @@ -22,13 +22,13 @@ const meta: Meta = { export default meta; -export const Preview: Story = (args) => ; +export const Preview: Story = (args) => ; Preview.args = { 'data-size': 'md', - count: 10, - maxCount: 9, 'data-color': 'accent', + count: 15, + maxCount: 9, }; export const Floating: Story = (args) => ( @@ -38,19 +38,24 @@ export const Floating: Story = (args) => ( gap: 'var(--ds-spacing-6)', }} > - + + - - + + + - - + + + - - + + + - - + + +
( backgroundColor: 'var(--ds-color-brand2-base-default)', }} /> - - + + +
( backgroundColor: 'var(--ds-color-brand2-base-default)', }} /> - - + + +
( backgroundColor: 'var(--ds-color-brand2-base-default)', }} /> - - + + +
( backgroundColor: 'var(--ds-color-brand2-base-default)', }} /> - +
); @@ -100,16 +108,16 @@ export const CustomPlacement: Story = (args) => ( gap: 'var(--ds-spacing-6)', }} > - + - +
); @@ -120,15 +128,18 @@ export const Status: Story = (args) => ( gap: 'var(--ds-spacing-4)', }} > - + + - - + + + - - + + + - +
); @@ -161,19 +172,22 @@ export const InButton: Story = (args) => ( }} >
); diff --git a/packages/react/src/components/Badge/Badge.tsx b/packages/react/src/components/Badge/Badge.tsx index ea1509965f..a07baa8880 100644 --- a/packages/react/src/components/Badge/Badge.tsx +++ b/packages/react/src/components/Badge/Badge.tsx @@ -1,5 +1,5 @@ import cl from 'clsx/lite'; -import { type HTMLAttributes, type ReactNode, forwardRef } from 'react'; +import { type HTMLAttributes, forwardRef } from 'react'; import type { Color } from '../../colors'; import type { DefaultProps } from '../../types'; import type { MergeRight } from '../../utilities'; @@ -19,22 +19,7 @@ export type BadgeProps = MergeRight< * The maximum number to display in the badge, when the count exceeds this number, the badge will display `{max}+` */ maxCount?: number; - /** - * The placement of the badge - * - * @default top-right - */ - placement?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'; - /** - * Use when badge is floating to change the position of the badge - * - * @default rectangle - */ - overlap?: 'circle' | 'rectangle'; - /** - * The badge will float on top of the children - */ - children?: ReactNode; + children?: never; } >; @@ -54,14 +39,7 @@ export type BadgeProps = MergeRight< * ``` */ export const Badge = forwardRef(function Badge( - { - className, - count, - maxCount, - overlap = 'rectangle', - placement = 'top-right', - ...rest - }, + { className, count, maxCount, ...rest }, ref, ) { return ( @@ -70,8 +48,6 @@ export const Badge = forwardRef(function Badge( data-count={ count && maxCount && count > maxCount ? `${maxCount}+` : count } - data-overlap={rest.children ? overlap : null} - data-placement={rest.children ? placement : null} ref={ref} {...rest} /> diff --git a/packages/react/src/components/Badge/BadgePosition.tsx b/packages/react/src/components/Badge/BadgePosition.tsx new file mode 100644 index 0000000000..240312c1a3 --- /dev/null +++ b/packages/react/src/components/Badge/BadgePosition.tsx @@ -0,0 +1,38 @@ +import cl from 'clsx/lite'; +import { type HTMLAttributes, forwardRef } from 'react'; +import type { DefaultProps } from '../../types'; +import type { MergeRight } from '../../utilities'; +export type BadgePositionProps = MergeRight< + DefaultProps & HTMLAttributes, + { + /** + * The placement of the badge + * + * @default top-right + */ + placement?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'; + /** + * Use when badge is floating to change the position of the badge + * + * @default rectangle + */ + overlap?: 'circle' | 'rectangle'; + } +>; + +export const BadgePosition = forwardRef( + function BadgePlacement( + { className, overlap = 'rectangle', placement = 'top-right', ...rest }, + ref, + ) { + return ( + + ); + }, +); diff --git a/packages/react/src/components/Badge/index.ts b/packages/react/src/components/Badge/index.ts index cd3531df2f..d9de42fc92 100644 --- a/packages/react/src/components/Badge/index.ts +++ b/packages/react/src/components/Badge/index.ts @@ -1,2 +1,10 @@ -export { Badge } from './Badge'; +import { Badge as BadgeElm } from './Badge'; +import { BadgePosition } from './BadgePosition'; + +const Badge = Object.assign(BadgeElm, { Position: BadgePosition }); + +Badge.Position.displayName = 'Badge.Position'; + +export { Badge, BadgePosition }; export type { BadgeProps } from './Badge'; +export type { BadgePositionProps } from './BadgePosition';