diff --git a/.changeset/metal-pigs-sing.md b/.changeset/metal-pigs-sing.md new file mode 100644 index 00000000000..a8c4be26fd9 --- /dev/null +++ b/.changeset/metal-pigs-sing.md @@ -0,0 +1,6 @@ +--- +"@navikt/ds-react": minor +"@navikt/ds-css": minor +--- + +InlineMessage: :tada: New component! Replaces `` variant as a standalone component. diff --git a/@navikt/core/css/config/_mappings.js b/@navikt/core/css/config/_mappings.js index b8ef0b221c9..40fe9fe0358 100644 --- a/@navikt/core/css/config/_mappings.js +++ b/@navikt/core/css/config/_mappings.js @@ -150,6 +150,11 @@ const StyleMappings = { }, { component: "InfoCard", main: "alert.css", dependencies: [typoCss] }, { component: "Ingress", main: typoCss }, + { + component: "InlineMessage", + main: "inline-message.css", + dependencies: [typoCss], + }, { component: "InternalHeader", main: "internalheader.css", diff --git a/@navikt/core/css/darkside/index.css b/@navikt/core/css/darkside/index.css index 3b8647e3c46..18f3a7908c2 100644 --- a/@navikt/core/css/darkside/index.css +++ b/@navikt/core/css/darkside/index.css @@ -35,6 +35,7 @@ @import "./expansioncard.darkside.css" layer(aksel.components); @import "./guide-panel.darkside.css" layer(aksel.components); @import "./help-text.darkside.css" layer(aksel.components); +@import "./inline-message.darkside.css" layer(aksel.components); @import "./internalheader.darkside.css" layer(aksel.components); @import "./link.darkside.css" layer(aksel.components); @import "./link-panel.darkside.css" layer(aksel.components); diff --git a/@navikt/core/css/darkside/inline-message.darkside.css b/@navikt/core/css/darkside/inline-message.darkside.css new file mode 100644 index 00000000000..9e46522c59d --- /dev/null +++ b/@navikt/core/css/darkside/inline-message.darkside.css @@ -0,0 +1,15 @@ +.aksel-inline-message { + display: flex; + gap: var(--ax-space-4); + color: var(--ax-text-default); +} + +.aksel-inline-message__icon { + color: var(--ax-text-subtle); + font-size: 1.5rem; + flex: 0 0 auto; + + .aksel-inline-message[data-size="small"] & { + font-size: 1.25rem; + } +} diff --git a/@navikt/core/css/index.css b/@navikt/core/css/index.css index 814867eb1bf..2eac577ad3e 100644 --- a/@navikt/core/css/index.css +++ b/@navikt/core/css/index.css @@ -14,6 +14,7 @@ @import "guide-panel.css"; @import "form/index.css"; @import "help-text.css"; +@import "inline-message.css"; @import "internalheader.css"; @import "link.css"; @import "link-anchor.css"; diff --git a/@navikt/core/css/inline-message.css b/@navikt/core/css/inline-message.css new file mode 100644 index 00000000000..b02fcd5aa97 --- /dev/null +++ b/@navikt/core/css/inline-message.css @@ -0,0 +1,45 @@ +.navds-inline-message { + display: flex; + gap: var(--a-spacing-1); +} + +.navds-inline-message[data-color="info"] { + color: var(--a-lightblue-900); + + & .navds-inline-message__icon { + color: var(--a-icon-info); + } +} + +.navds-inline-message[data-color="success"] { + color: var(--a-green-900); + + & .navds-inline-message__icon { + color: var(--a-icon-success); + } +} + +.navds-inline-message[data-color="danger"] { + color: var(--a-red-900); + + & .navds-inline-message__icon { + color: var(--a-icon-danger); + } +} + +.navds-inline-message[data-color="warning"] { + color: var(--a-orange-900); + + & .navds-inline-message__icon { + color: var(--a-icon-warning); + } +} + +.navds-inline-message__icon { + font-size: 1.5rem; + flex: 0 0 auto; + + .navds-inline-message[data-size="small"] & { + font-size: 1.25rem; + } +} diff --git a/@navikt/core/react/package.json b/@navikt/core/react/package.json index fda3460c682..a963eaed754 100644 --- a/@navikt/core/react/package.json +++ b/@navikt/core/react/package.json @@ -624,6 +624,16 @@ "default": "./cjs/form/form-progress/index.js" } }, + "./InlineMessage": { + "import": { + "types": "./esm/inline-message/index.d.ts", + "default": "./esm/inline-message/index.js" + }, + "require": { + "types": "./cjs/inline-message/index.d.ts", + "default": "./cjs/inline-message/index.js" + } + }, "./GlobalAlert": { "import": { "types": "./esm/alert/global-alert/index.d.ts", diff --git a/@navikt/core/react/src/index.ts b/@navikt/core/react/src/index.ts index d4c3f235b87..f5bab1b7768 100644 --- a/@navikt/core/react/src/index.ts +++ b/@navikt/core/react/src/index.ts @@ -163,6 +163,7 @@ export { type LinkCardIconProps, type LinkCardImageProps, } from "./link-card"; +export { InlineMessage, type InlineMessageProps } from "./inline-message"; export { GlobalAlert, type GlobalAlertProps, diff --git a/@navikt/core/react/src/inline-message/InlineMessage.stories.tsx b/@navikt/core/react/src/inline-message/InlineMessage.stories.tsx new file mode 100644 index 00000000000..55ceb5ad916 --- /dev/null +++ b/@navikt/core/react/src/inline-message/InlineMessage.stories.tsx @@ -0,0 +1,119 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import React from "react"; +import { VStack } from "../layout/stack"; +import { Link } from "../link"; +import { renderStoriesForChromatic } from "../util/renderStoriesForChromatic"; +import { InlineMessage } from "./index"; + +const meta: Meta = { + title: "ds-react/InlineMessage", + component: InlineMessage, + parameters: { + chromatic: { disable: true }, + }, +}; + +export default meta; + +type Story = StoryObj; + +const variants = ["info", "success", "warning", "error"] as const; + +export const Default: Story = { + render: (props) => { + return ( + + {props.children ?? "InlineMessage content"} + + ); + }, + + args: { + children: "Id elit esse enim reprehenderit enim nisi veniam nostrud.", + variant: "warning", + }, + argTypes: { + variant: { + control: { type: "select" }, + options: variants, + }, + }, +}; + +export const SizeSmall: Story = { + render: () => { + return ( + + + + ); + }, +}; + +export const Compositions: Story = { + render: () => { + return ( + + {variants.map((variant) => ( + + + + ))} + + ); + }, +}; + +export const WrappingTitle: Story = { + render: () => { + return ( + + + + ); + }, +}; + +export const AsLink: Story = { + render: () => { + return ( + + This is a link inside the InlineMessage + + ); + }, +}; + +export const Chromatic = renderStoriesForChromatic({ + Default, + Compositions, + WrappingTitle, + SizeSmall, +}); + +export const ChromaticLight = renderStoriesForChromatic({ + Default, + Compositions, + WrappingTitle, + SizeSmall, +}); +ChromaticLight.globals = { theme: "light", mode: "darkside" }; + +export const ChromaticDark = renderStoriesForChromatic({ + Default, + Compositions, + WrappingTitle, + SizeSmall, +}); +ChromaticDark.globals = { theme: "dark", mode: "darkside" }; + +function DemoContent() { + return ( + + Lorem ipsum dolor, sit amet consectetur adipisicing elit. Iure unde, + repudiandae, deleniti exercitationem quod aut veniam sint officiis + necessitatibus nulla nostrum voluptatem Test{" "} + facilis! Commodi, nobis tempora quibusdam temporibus nulla quam. + + ); +} diff --git a/@navikt/core/react/src/inline-message/icon/InlineMessageIcon.tsx b/@navikt/core/react/src/inline-message/icon/InlineMessageIcon.tsx new file mode 100644 index 00000000000..7a42e5e3feb --- /dev/null +++ b/@navikt/core/react/src/inline-message/icon/InlineMessageIcon.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { + CheckmarkCircleFillIcon, + ExclamationmarkTriangleFillIcon, + InformationSquareFillIcon, + XMarkOctagonFillIcon, +} from "@navikt/aksel-icons"; +import { useRenameCSS } from "../../theme/Theme"; +import { useI18n } from "../../util/i18n/i18n.hooks"; + +const VARIANT_ICONS = { + info: InformationSquareFillIcon, + success: CheckmarkCircleFillIcon, + warning: ExclamationmarkTriangleFillIcon, + error: XMarkOctagonFillIcon, +} as const; + +function InlineMessageIcon({ + variant, +}: { + variant: "info" | "success" | "warning" | "error"; +}) { + const translate = useI18n("Alert"); + const { cn } = useRenameCSS(); + + if (!(variant in VARIANT_ICONS)) { + return null; + } + + const Icon = VARIANT_ICONS[variant]; + + return ( + + ); +} + +export { InlineMessageIcon }; diff --git a/@navikt/core/react/src/inline-message/index.ts b/@navikt/core/react/src/inline-message/index.ts new file mode 100644 index 00000000000..d572c992dcd --- /dev/null +++ b/@navikt/core/react/src/inline-message/index.ts @@ -0,0 +1,3 @@ +"use client"; +export { InlineMessage } from "./root/InlineMessage"; +export type { InlineMessageProps } from "./root/InlineMessage"; diff --git a/@navikt/core/react/src/inline-message/root/InlineMessage.tsx b/@navikt/core/react/src/inline-message/root/InlineMessage.tsx new file mode 100644 index 00000000000..b1f54e67bd4 --- /dev/null +++ b/@navikt/core/react/src/inline-message/root/InlineMessage.tsx @@ -0,0 +1,74 @@ +import React, { forwardRef } from "react"; +import { useRenameCSS, useThemeInternal } from "../../theme/Theme"; +import { BodyShort } from "../../typography"; +import type { OverridableComponent } from "../../util"; +import { InlineMessageIcon } from "../icon/InlineMessageIcon"; + +interface InlineMessageProps extends React.HTMLAttributes { + /** + * InlineMessage variant. + */ + variant: "info" | "success" | "warning" | "error"; + /** + * InlineMessage size. + * @default "medium" + */ + size?: "medium" | "small"; +} + +/** + * InlineMessage is used to display important messages together with other content. + * @see [📝 Documentation](https://aksel.nav.no/komponenter/core/inline-message) + * @see 🏷️ {@link InlineMessageProps} + * @see [🤖 OverridableComponent](https://aksel.nav.no/grunnleggende/kode/overridablecomponent) support + * @example + * ```jsx + * + * Inline Errormessage + * + * ``` + * + * @example + * As a link + * ```jsx + * + * Inline Errormessage + * + * ``` + */ +const InlineMessage: OverridableComponent = + forwardRef( + ( + { + as: Component = "div", + children, + className, + variant, + size = "medium", + ...restProps + }: InlineMessageProps & { as?: React.ElementType }, + forwardedRef, + ) => { + const { cn } = useRenameCSS(); + const themeContext = useThemeInternal(false); + + return ( + + + {children} + + ); + }, + ); + +export { InlineMessage }; +export type { InlineMessageProps };