Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Ny komponent ShowMore #2297

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions .changeset/khaki-hounds-breathe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@navikt/ds-react": minor
"@navikt/ds-css": minor
---

:sparkles: Ny komponent ShowMore
5 changes: 5 additions & 0 deletions @navikt/core/css/config/_mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ const StyleMappings = {
{ component: "Radio", main: formCss, dependencies: [typoCss] },
{ component: "RadioGroup", main: formCss, dependencies: [typoCss] },
{ component: "ReadMore", main: "read-more.css", dependencies: [typoCss] },
{
component: "ShowMore",
main: "show-more.css",
dependencies: [typoCss, "button.css"],
},
{ component: "Search", main: formCss, dependencies: [typoCss] },
{ component: "Select", main: formCss, dependencies: [typoCss] },
{ component: "Skeleton", main: "skeleton.css", dependencies: [] },
Expand Down
1 change: 1 addition & 0 deletions @navikt/core/css/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
@import "panel.css";
@import "link-panel.css";
@import "read-more.css";
@import "show-more.css";
@import "skeleton.css";
@import "stepper.css";
@import "table.css";
Expand Down
58 changes: 58 additions & 0 deletions @navikt/core/css/show-more.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.navds-show-more {
position: relative;
border-radius: var(--a-border-radius-large);
padding: var(--a-spacing-4);
height: fit-content; /* in case the parent is stretching */
display: flex;
flex-direction: column-reverse;
}

.navds-show-more--inline {
padding: 0;
}

.navds-show-more--default,
.navds-show-more--default .navds-show-more__button-wrapper {
background: var(--a-surface-default);
}

.navds-show-more--subtle,
.navds-show-more--subtle .navds-show-more__button-wrapper {
background: var(--a-surface-subtle);
}

.navds-show-more--info,
.navds-show-more--info .navds-show-more__button-wrapper {
background: var(--a-surface-info-subtle);
}

.navds-show-more--closed .navds-show-more__content {
--__ac-show-more-mask: linear-gradient(rgba(0 0 0 / 1) 1.25rem, rgba(0 0 0 / 0) calc(100% - 0.5rem));

-webkit-mask-image: var(--__ac-show-more-mask);
mask-image: var(--__ac-show-more-mask);
overflow: hidden;
}

.navds-show-more__button-section {
text-align: center;
left: 0;
right: 0;
bottom: var(--a-spacing-4);
margin-top: var(--a-spacing-4);
z-index: 1;
}

.navds-show-more--inline .navds-show-more__button-section {
bottom: 0;
}

.navds-show-more--closed .navds-show-more__button-section {
position: absolute;
}

.navds-show-more__button-wrapper {
display: inline-block;
background: var(--a-bg-default);
border-radius: var(--ac-button-border-radius, var(--a-border-radius-medium)); /* same as Button */
}
1 change: 1 addition & 0 deletions @navikt/core/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from "./panel";
export * from "./popover";
export * from "./provider";
export * from "./read-more";
export * from "./show-more";
export * from "./skeleton";
export * from "./stepper";
export * from "./table";
Expand Down
6 changes: 3 additions & 3 deletions @navikt/core/react/src/read-more/ReadMore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@ export interface ReadMoreProps
header: React.ReactNode;
/**
* Opens component if 'true', closes if 'false'
* Using this props removes automatic control of open-state
* Using this prop removes automatic control of open-state
*/
open?: boolean;
/**
* Defaults the accordion to opened state
* Initially open
* @default false
*/
defaultOpen?: boolean;
/**
* Changes fontsize for content
* @default false
* @default medium
*/
size?: "medium" | "small";
}
Expand Down
117 changes: 117 additions & 0 deletions @navikt/core/react/src/show-more/ShowMore.tsx
HalvorHaugan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import React, { forwardRef, useState } from "react";
import cl from "clsx";
import { ChevronDownIcon, ChevronUpIcon } from "@navikt/aksel-icons";
import { Button, OverridableComponent } from "..";

interface ShowMoreBaseProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "onClick"> {
children: React.ReactNode;
/**
* Opens component if 'true', collapses if 'false'.
* Using this prop removes automatic control of open-state.
*/
open?: boolean;
HalvorHaugan marked this conversation as resolved.
Show resolved Hide resolved
/**
* Changes button size
* @default medium
*/
size?: "medium" | "small";
/**
* Changes background color. Variant 'inline' is transparent and has no padding.
* @default inline
*/
variant?: "inline" | "default" | "subtle" | "info";
/**
* Custom collapsed height.
* @default 13.5rem
*/
collapsedHeight?: `${number}${string}` | number;
HalvorHaugan marked this conversation as resolved.
Show resolved Hide resolved
/** Click handler for the show more/less button */
onClick?: React.MouseEventHandler<HTMLButtonElement>;
/** Label for the content */
"aria-label"?: string;
/** ID of an element that labels the content */
"aria-labelledby"?: string;
}

export type ShowMoreProps = ShowMoreBaseProps &
({ "aria-label": string } | { "aria-labelledby": string });

/**
* A component for partially hiding less important content.
*
* @see [📝 Documentation](https://aksel.nav.no/komponenter/core/show-more)
* @see 🏷️ {@link ShowMoreProps}
*
* @example
* <ShowMore aria-label="Facts about toads">
* Toads have dry, leathery skin, short legs, and large bumps covering the parotoid glands.
* </ShowMore>
*/
export const ShowMore: OverridableComponent<ShowMoreProps, HTMLDivElement> =
forwardRef(
(
{
as: Component = "div",
children,
open,
size = "medium",
variant = "inline",
collapsedHeight = "13.5rem",
className,
onClick,
"aria-label": ariaLabel,
...rest
},
ref
) => {
const [internalOpen, setInternalOpen] = useState(false);

const isOpen = open ?? internalOpen;
const ChevronIcon = isOpen ? ChevronUpIcon : ChevronDownIcon;

return (
<Component
ref={ref}
className={cl(
"navds-show-more",
`navds-show-more--${variant}`,
className,
{ "navds-show-more--closed": !isOpen }
)}
{...rest}
>
<div className="navds-show-more__button-section">
<div className="navds-show-more__button-wrapper">
<Button
type="button"
variant="secondary-neutral"
size={size}
className="navds-show-more__button"
onClick={(e) => {
if (open === undefined) setInternalOpen((isOpen) => !isOpen);
onClick?.(e);
}}
aria-expanded={isOpen}
aria-label={ariaLabel}
HalvorHaugan marked this conversation as resolved.
Show resolved Hide resolved
icon={<ChevronIcon aria-hidden />}
iconPosition="right"
>
{isOpen ? "Vis mindre" : "Vis mer"}
</Button>
</div>
</div>

<div
className="navds-show-more__content"
style={isOpen ? {} : { height: collapsedHeight }}
aria-hidden={!isOpen}
>
{children}
</div>
</Component>
);
}
);

export default ShowMore;
2 changes: 2 additions & 0 deletions @navikt/core/react/src/show-more/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as ShowMore } from "./ShowMore";
export { type ShowMoreProps } from "./ShowMore";
120 changes: 120 additions & 0 deletions @navikt/core/react/src/show-more/showmore.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { Meta, StoryFn } from "@storybook/react";
import React, { useState } from "react";
import { ShowMore, ShowMoreProps } from ".";
import { BodyLong, HStack, Heading } from "..";

const meta: Meta<typeof ShowMore> = {
title: "ds-react/ShowMore",
component: ShowMore,
};
export default meta;

const variants = ["inline", "default", "subtle", "info"] as const;

const content = (
<BodyLong style={{ maxWidth: 400 }}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nisl eros,
vestibulum et dui non, malesuada egestas nisi. Suspendisse neque ante,
volutpat a ante eu, blandit scelerisque ex. Integer sit amet sapien eget mi
molestie ultricies sit amet dictum metus. Morbi consequat urna tristique
consectetur rhoncus. Phasellus ac facilisis libero. Maecenas quis nisi
elementum, tincidunt justo a, fermentum massa. Vestibulum tincidunt in dolor
eget rhoncus. Aliquam vehicula, nisl id sollicitudin congue, tortor eros
tempor odio, a eleifend est ex vitae libero.
</BodyLong>
);

export const Default: StoryFn<{
controlled: boolean;
size: "medium" | "small";
variant: ShowMoreProps["variant"];
}> = ({ controlled, ...rest }) => {
const [state, setState] = useState(false);

return (
<ShowMore
open={controlled ? state : undefined}
onClick={() => setState((x) => !x)}
aria-label="Lorem ipsum"
{...rest}
>
{content}
</ShowMore>
);
};
Default.args = {
controlled: false,
};
Default.argTypes = {
size: {
options: ["medium", "small"],
control: { type: "radio" },
},
variant: {
options: variants,
control: { type: "radio" },
},
};

export const Small = () => (
<ShowMore aria-label="Lorem ipsum" size="small">
{content}
</ShowMore>
);

export const CustomHeight = () => (
<HStack gap="8">
<ShowMore as="aside" aria-label="Tabellvisning" collapsedHeight="8rem">
{content}
</ShowMore>
<ShowMore aria-label="Tabellvisning" collapsedHeight={90} size="small">
{content}
</ShowMore>
</HStack>
);

export const Variants = () => (
<HStack gap="4" style={{ padding: "1rem" }}>
<HStack
gap="4"
align="start"
style={{
padding: "2rem",
backgroundImage:
"linear-gradient(45deg, #f5f50050 25%, transparent 25%), linear-gradient(-45deg, #f5f50050 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #f5f50050 75%), linear-gradient(-45deg, transparent 75%, #f5f50050 75%)",
backgroundSize: "20px 20px",
backgroundPosition: "0 0, 0 10px, 10px -10px, -10px 0px",
}}
>
<div style={{ border: "1px dashed #FF000050", maxWidth: "400px" }}>
<ShowMore aria-label="Lorem ipsum" variant="inline">
<Heading size="small">inline</Heading>
<BodyLong spacing>
This is the default variant. It does not have padding. The dashed
border is not part of the component, but is added to the story to
make it easier to see that there is no padding.
</BodyLong>
{content}
</ShowMore>
</div>
<ShowMore aria-label="Lorem ipsum" variant="default">
<Heading size="small">default</Heading>
{content}
</ShowMore>
</HStack>
{variants
.filter((v) => !["inline", "default"].includes(v))
.map((variant) => (
<ShowMore aria-label="Lorem ipsum" key={variant} variant={variant}>
<Heading size="small">{variant}</Heading>
{content}
</ShowMore>
))}
</HStack>
);

export const Open = () => (
<ShowMore aria-label="Lorem ipsum" open>
{content}
</ShowMore>
);
35 changes: 35 additions & 0 deletions aksel.nav.no/website/pages/eksempler/showmore/customHeight.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { BodyLong, Heading, ShowMore } from "@navikt/ds-react";
import { withDsExample } from "components/website-modules/examples/withDsExample";

const Example = () => {
return (
<div style={{ minHeight: "316px" }}>
<ShowMore aria-label="Facts" collapsedHeight="8rem">
<Heading size="small">Facts</Heading>
<BodyLong>
Did you hear that? They&apos;ve shut down the main reactor. We&apos;ll
be destroyed for sure. This is madness! We&apos;re doomed!
There&apos;ll be no escape for the Princess this time. What&apos;s
that? Artoo! Artoo-Detoo, where are you? At last! Where have you been?
They&apos;re heading in this direction. What are we going to do?
We&apos;ll be sent to the spice mine of Kessel or smashed into who
knows what! Wait a minute, where are you going? The Death Star plans
are not in the main computer. Where are those transmissions you
intercepted? What have you done with those plans? We intercepted no
transmissions. Aaah....This is a consular ship.
</BodyLong>
</ShowMore>
</div>
);
};

export default withDsExample(Example);

/* Storybook story */
export const Demo = {
render: Example,
};

export const args = {
index: 5,
};
Loading