From e64b0b6de34aa805189f2f14b66b852d739b2ea4 Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Fri, 26 Jan 2024 17:56:21 +0100 Subject: [PATCH] :sparkles: #11 - feat: add datagrid component --- src/components/badge/badge.scss | 13 + src/components/badge/badge.stories.tsx | 17 ++ src/components/badge/badge.tsx | 19 ++ src/components/badge/index.ts | 1 + src/components/button/button.scss | 1 + src/components/datagrid/datagrid.scss | 143 +++++++++++ src/components/datagrid/datagrid.stories.tsx | 199 +++++++++++++++ src/components/datagrid/datagrid.tsx | 227 ++++++++++++++++++ src/components/datagrid/index.ts | 1 + src/components/form/input/input.scss | 1 + src/components/form/select/select.scss | 2 + src/components/index.ts | 2 + .../layout/container/container.scss | 2 + src/components/page/page.scss | 4 +- src/components/page/page.stories.tsx | 103 +++++++- src/components/paginator/paginator.scss | 10 +- src/components/paginator/paginator.tsx | 33 ++- src/components/toolbar/toolbar.scss | 5 + src/components/toolbar/toolbar.tsx | 6 + src/components/typography/h3/h3.tsx | 6 +- src/components/typography/p/p.scss | 10 +- src/components/typography/p/p.tsx | 9 +- src/settings/tokens.css | 4 + 23 files changed, 790 insertions(+), 28 deletions(-) create mode 100644 src/components/badge/badge.scss create mode 100644 src/components/badge/badge.stories.tsx create mode 100644 src/components/badge/badge.tsx create mode 100644 src/components/badge/index.ts create mode 100644 src/components/datagrid/datagrid.scss create mode 100644 src/components/datagrid/datagrid.stories.tsx create mode 100644 src/components/datagrid/datagrid.tsx create mode 100644 src/components/datagrid/index.ts diff --git a/src/components/badge/badge.scss b/src/components/badge/badge.scss new file mode 100644 index 00000000..1815f50c --- /dev/null +++ b/src/components/badge/badge.scss @@ -0,0 +1,13 @@ +.mykn-badge { + background-color: var(--theme-color-primary-200); + align-items: center; + border-radius: var(--border-radus-m); + display: inline-flex; + font-family: Inter, sans-serif; + font-size: var(--typography-font-size-body-xs); + font-weight: var(--typography-font-weight-normal); + height: var(--typography-line-height-body-s); + justify-content: center; + line-height: var(--typography-line-height-body-xs); + padding: 0 var(--spacing-h-s); +} diff --git a/src/components/badge/badge.stories.tsx b/src/components/badge/badge.stories.tsx new file mode 100644 index 00000000..9384cd14 --- /dev/null +++ b/src/components/badge/badge.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { Badge } from "./badge"; + +const meta = { + title: "Typography/Badge", + component: Badge, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const BadgeComponent: Story = { + args: { + children: "The quick brown fox jumps over the lazy dog.", + }, +}; diff --git a/src/components/badge/badge.tsx b/src/components/badge/badge.tsx new file mode 100644 index 00000000..790b0d42 --- /dev/null +++ b/src/components/badge/badge.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +import "./badge.scss"; + +export type BadgeProps = React.PropsWithChildren<{ + // Props here. +}>; + +/** + * Badge component + * @param children + * @param props + * @constructor + */ +export const Badge: React.FC = ({ children, ...props }) => ( +
+ {children} +
+); diff --git a/src/components/badge/index.ts b/src/components/badge/index.ts new file mode 100644 index 00000000..80844a4e --- /dev/null +++ b/src/components/badge/index.ts @@ -0,0 +1 @@ +export * from "./badge"; diff --git a/src/components/button/button.scss b/src/components/button/button.scss index 657660f6..40473592 100644 --- a/src/components/button/button.scss +++ b/src/components/button/button.scss @@ -44,6 +44,7 @@ ); --mykn-button-padding-v: 0; --mykn-button-padding-h: 0; + flex-shrink: 0; } &--variant-primary { diff --git a/src/components/datagrid/datagrid.scss b/src/components/datagrid/datagrid.scss new file mode 100644 index 00000000..903d5bf2 --- /dev/null +++ b/src/components/datagrid/datagrid.scss @@ -0,0 +1,143 @@ +@use "../../settings/constants"; + +.mykn-datagrid { + background-color: var(--typography-color-background); + border-radius: var(--border-radus-m); + + &__table { + border-spacing: 0; + width: 100%; + } + + &__caption { + padding: var(--spacing-v-m) var(--spacing-h-m); + text-align: start; + } + + &__head { + background-color: var(--typography-color-background); + position: sticky; + top: 0; + } + + &__head &__row:first-child &__cell { + border-top: 1px solid var(--theme-shade-300); + } + + &__cell { + border-bottom: 1px solid var(--theme-shade-300); + box-sizing: border-box; + padding: var(--spacing-v-m) var(--spacing-h-m); + } + + &__cell--type-boolean { + text-align: center; + } + + &__cell--type-number { + text-align: end; + } + + &__foot { + position: sticky; + bottom: 0; + } + + &__foot &__cell { + border-bottom: none; + padding-top: 0; + padding-bottom: 0; + } + + @media screen and (max-width: constants.$breakpoint-desktop - 1px) { + background-color: transparent; + overflow: visible; + + &__table { + display: block; + } + + &__caption { + background-color: var(--typography-color-background); + border-radius: var(--border-radus-m); + display: block; + } + + &__head { + display: none; + } + + &__body { + display: block; + } + + &__row { + background-color: var( + --typography-color-background + ); //border-radius: var(--border-radus-m); + display: flex; + flex-wrap: wrap; + + &:nth-child(even) { + background-color: var(--theme-shade-100); + } + } + + &__row:nth-child(even) &__cell { + border-bottom: 1px solid var(--typography-color-background); + } + + &__cell { + display: flex; + flex-direction: column; + gap: var(--spacing-h-m); + width: 100%; + position: relative; + + .mykn-p { + font-weight: var(--typography-font-weight-bold); + width: 100%; + } + + &:before { + color: var(--theme-shade-700); + content: attr(aria-description); + font-family: Inter, sans-serif; + font-size: var(--typography-font-size-body-xs); + font-weight: var(--typography-font-weight-normal); + line-height: var(--typography-line-height-body-xs); + display: block; + text-align: start; + width: 40%; + } + + &:first-child .mykn-a:has(.mykn-icon) { + float: right; + } + } + + &__foot { + display: flex; + } + + &__foot &__row { + width: 100%; + } + + &__foot &__cell { + padding: 0; + + &:before { + display: none; + } + } + + .mykn-toolbar { + border-radius: var(--border-radus-m); + } + + .mykn-paginator .mykn-icon--spin:first-child { + display: none; + } + } +} diff --git a/src/components/datagrid/datagrid.stories.tsx b/src/components/datagrid/datagrid.stories.tsx new file mode 100644 index 00000000..55017f0d --- /dev/null +++ b/src/components/datagrid/datagrid.stories.tsx @@ -0,0 +1,199 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React, { useEffect, useState } from "react"; + +import { Page } from "../page"; +import { PaginatorProps } from "../paginator"; +import { DataGrid } from "./datagrid"; + +const meta = { + title: "Data/DataGrid", + component: DataGrid, + decorators: [ + (Story) => ( + + + + ), + ], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const DataridComponent = { + args: { + booleanProps: { + labelTrue: "This value is true", + labelFalse: "This value is false", + }, + paginatorProps: { + count: 100, + page: 1, + pageSize: 10, + pageSizeOptions: [ + { label: 10 }, + { label: 20 }, + { label: 30 }, + { label: 40 }, + { label: 50 }, + ], + labelLoading: "Loading", + labelPagination: "pagination", + labelCurrentPageRange: "{pageStart} - {pageEnd} of {pageCount}", + labelGoToPage: "Go to", + labelPageSize: "Show rows", + labelPrevious: "Go to previous page", + labelNext: "Go to next page", + }, + results: [ + { + url: "https://www.example.com", + Omschrijving: "Afvalpas vervangen", + Versie: 2, + Actief: false, + Toekomstig: false, + Concept: true, + }, + { + url: "https://www.example.com", + Omschrijving: "Erfpacht wijzigen", + Versie: 4, + Actief: true, + Toekomstig: true, + Concept: true, + }, + { + url: "https://www.example.com", + Omschrijving: "Dakkapel vervangen", + Versie: 1, + Actief: false, + Toekomstig: false, + Concept: false, + }, + { + url: "https://www.example.com", + Omschrijving: "Dakkapel vervangen", + Versie: 4, + Actief: true, + Toekomstig: true, + Concept: true, + }, + { + url: "https://www.example.com", + Omschrijving: "Erfpacht wijzigen", + Versie: 2, + Actief: false, + Toekomstig: false, + Concept: true, + }, + { + url: "https://www.example.com", + Omschrijving: "Dakkapel vervangen", + Versie: 4, + Actief: true, + Toekomstig: true, + Concept: true, + }, + { + url: "https://www.example.com", + Omschrijving: "Erfpacht wijzigen", + Versie: 1, + Actief: false, + Toekomstig: false, + Concept: false, + }, + { + url: "https://www.example.com", + Omschrijving: "Dakkapel vervangen", + Versie: 1, + Actief: false, + Toekomstig: false, + Concept: false, + }, + ], + title: "Posts", + urlFields: ["url"], + }, +}; + +export const DatagridOnMobile: Story = { + ...DataridComponent, + parameters: { + viewport: { defaultViewport: "mobile1" }, + chromatic: { + viewports: ["mobile1"], + }, + }, +}; + +export const JSONPlaceholderExample: Story = { + args: { + booleanProps: { + labelTrue: "This value is true", + labelFalse: "This value is false", + }, + paginatorProps: { + count: 100, + page: 1, + pageSize: 10, + pageSizeOptions: [ + { label: 10 }, + { label: 20 }, + { label: 30 }, + { label: 40 }, + { label: 50 }, + ], + labelLoading: "Loading", + labelPagination: "pagination", + labelCurrentPageRange: "{pageStart} - {pageEnd} of {pageCount}", + labelGoToPage: "Go to", + labelPageSize: "Show rows", + labelPrevious: "Go to previous page", + labelNext: "Go to next page", + }, + results: [], + title: "Posts", + }, + render: (args) => { + const [loading, setLoading] = useState(false); + const [page, setPage] = useState(args.paginatorProps?.page || 1); + const [pageSize, setPageSize] = useState( + args.paginatorProps?.pageSize || 10, + ); + const [results, setResults] = useState(args.results); + const paginatorProps = args.paginatorProps as PaginatorProps; + + paginatorProps.pageSize = pageSize; + + useEffect(() => { + setLoading(true); + const index = page - 1; + const abortController = new AbortController(); + + fetch("https://jsonplaceholder.typicode.com/posts", { + signal: abortController.signal, + }) + .then((response) => response.json()) + .then((data) => { + // Paginate locally for demonstration purposes. + const posts = data.slice( + index * pageSize, + index * pageSize + pageSize, + ); + setResults(posts); + setLoading(false); + }); + + return () => { + abortController.abort(); + setLoading(false); + }; + }, [page, pageSize]); + + paginatorProps.loading = loading; + paginatorProps.onPageChange = (page) => setPage(page); + paginatorProps.onPageSizeChange = async (pageSize) => setPageSize(pageSize); + + return ; + }, +}; diff --git a/src/components/datagrid/datagrid.tsx b/src/components/datagrid/datagrid.tsx new file mode 100644 index 00000000..6e374af6 --- /dev/null +++ b/src/components/datagrid/datagrid.tsx @@ -0,0 +1,227 @@ +import clsx from "clsx"; +import React, { useId } from "react"; + +import { Badge, BadgeProps } from "../badge"; +import { Boolean, BooleanProps } from "../boolean"; +import { Outline } from "../icon"; +import { Paginator, PaginatorProps } from "../paginator"; +import { Toolbar } from "../toolbar"; +import { A, AProps, H3, P } from "../typography"; +import "./datagrid.scss"; + +/** Matches a URL. */ +const REGEX_URL = /https?:\/\/[^\s]+$/; + +export type RowData = Record; + +export type DataGridProps = Omit< + React.HTMLAttributes, + "results" +> & { + /** The results (after pagination), only primitive types supported for now. */ + results: RowData[]; + + /** A `string[]` containing the keys in `results` to show data for. */ + fields?: string[]; + + /** + * A `string[]` containing the fields which should be used to detect the url + * of a row. Fields specified in this array won't be rendered, instead: the + * first (non url) field is rendered as link (`A`) with `href` set to the + * resolved url of the row. + */ + urlFields?: string[]; + + /** Props for A. */ + aProps?: Omit; + + /** Props for Badge. */ + badgeProps?: BadgeProps; + + /** Props for Boolean. */ + booleanProps?: Omit; + + /** If set, the paginator is enabled. */ + paginatorProps?: PaginatorProps; + + /** A title for the datagrid. */ + title?: string; +}; + +/** + * DataGrid component + * @param aProps + * @param badgeProps + * @param booleanProps + * @param paginatorProps + * @param results + * @param fields + * @param title + * @param urlFields + * @param props + * @constructor + */ +export const DataGrid: React.FC = ({ + aProps, + badgeProps, + booleanProps, + results, + fields = results?.length ? Object.keys(results[0]) : [], + paginatorProps, + title = "", + urlFields = [ + "absolute_url", + "get_absolute_url", + "href", + "get_href", + "url", + "get_url", + ], + ...props +}) => { + const id = useId(); + const renderableFields = fields.filter((f) => !urlFields.includes(f)); + const captions = renderableFields.map((f) => field2Caption(f as string)); + const titleId = title ? `${id}-caption` : undefined; + + /** + * Renders a cell based on type of `rowData[field]`. + * @param rowData + * @param field + * @param index + */ + const renderCell = (rowData: RowData, field: string, index: number) => { + const fieldIndex = renderableFields.indexOf(field); + const urlField = urlFields.find((f) => String(rowData[f]).match(REGEX_URL)); + const rowUrl = urlField ? rowData[urlField] : null; + const data = rowData[field]; + const type = typeof data; + + // Explicit link: passed as URL without being set as urlField. + const isExplicitLink = String(data).match(REGEX_URL); + + // Implicit link: first column and `rowUrl` resolved using `urlFields`. + const isImplicitLink = rowUrl && fieldIndex === 0; + + // Cell should be a link is truthy. + const link = isExplicitLink + ? String(data) + : isImplicitLink + ? String(rowUrl) + : ""; + + let contents: React.ReactNode = data; + switch (type) { + case "boolean": + contents = ( + + ); + break; + case "number": + contents = {data}; + } + + return ( + + {isExplicitLink ? ( + + {contents} + + ) : ( + <> + {isImplicitLink ? ( +

+ + + +   + {contents} +

+ ) : ( +

{contents}

+ )} + + )} + + ); + }; + + return ( +
+ {/* Caption */} + + {title && ( + + )} + + {/* Headings */} + + + {captions.map((caption) => ( + + ))} + + + + {/* Cells */} + + {results.map((rowData, index) => ( + /** + * FIXME: This effectively still uses index as keys which might lead to issues. + * @see {@link https://react.dev/learn/rendering-lists#rules-of-keys|Rules of keys} + */ + + {renderableFields.map((field) => + renderCell(rowData, String(field), index), + )} + + ))} + + + {/* Paginator */} + {paginatorProps && ( + + + + + + )} +
+

{title}

+
+

+ {caption} +

+
+ + + +
+
+ ); +}; + +/** + * Converts "field_name" to "FIELD NAME". + * @param field + */ +const field2Caption = (field: string): string => + String(field).replaceAll("_", " ").toUpperCase(); diff --git a/src/components/datagrid/index.ts b/src/components/datagrid/index.ts new file mode 100644 index 00000000..f32f9692 --- /dev/null +++ b/src/components/datagrid/index.ts @@ -0,0 +1 @@ +export * from "./datagrid"; diff --git a/src/components/form/input/input.scss b/src/components/form/input/input.scss index e4c1d9fe..b1986c03 100644 --- a/src/components/form/input/input.scss +++ b/src/components/form/input/input.scss @@ -8,6 +8,7 @@ color: var(--typography-color-body); font-family: Inter, sans-serif; font-size: var(--typography-font-size-body-s); + font-weight: var(--typography-font-weight-normal); line-height: var(--typography-line-height-body-s); padding: var(--spacing-v-s) var(--spacing-h-s); position: relative; diff --git a/src/components/form/select/select.scss b/src/components/form/select/select.scss index 28a55bdc..742557a5 100644 --- a/src/components/form/select/select.scss +++ b/src/components/form/select/select.scss @@ -10,6 +10,7 @@ justify-content: space-between; font-family: Inter, sans-serif; font-size: var(--typography-font-size-body-s); + font-weight: var(--typography-font-weight-normal); line-height: var(--typography-line-height-body-s); padding: var(--spacing-v-s) var(--spacing-h-s); width: min(320px, 100%); @@ -80,6 +81,7 @@ font-weight: var(--mykn-option-font-weight); line-height: var(--typography-line-height-body-s); padding: 0 var(--spacing-h-s); + text-align: start; white-space: nowrap; &[aria-selected="true"] { diff --git a/src/components/index.ts b/src/components/index.ts index 9b339e97..d79c88dd 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,7 +1,9 @@ // Auto-generated file. Do not modify manually. +export * from "./badge"; export * from "./boolean"; export * from "./button"; export * from "./card"; +export * from "./datagrid"; export * from "./dropdown"; export * from "./form"; export * from "./icon"; diff --git a/src/components/layout/container/container.scss b/src/components/layout/container/container.scss index 926cde19..6f343203 100644 --- a/src/components/layout/container/container.scss +++ b/src/components/layout/container/container.scss @@ -1,4 +1,6 @@ .mykn-container { + container-name: container; + container-type: inline-size; margin: 0 auto; max-width: 1240px; width: 100%; diff --git a/src/components/page/page.scss b/src/components/page/page.scss index 57fd9f02..9bd01a32 100644 --- a/src/components/page/page.scss +++ b/src/components/page/page.scss @@ -3,11 +3,11 @@ .mykn-page { background-color: var(--theme-color-primary-200); container-name: page; - container-type: size; + container-type: inline-size; box-sizing: border-box; padding: var(--spacing-h-xl); width: 100%; - height: 100%; + min-height: 100%; @media screen and (min-width: constants.$breakpoint-desktop) { padding: var(--spacing-v-xl) var(--spacing-h-xl); diff --git a/src/components/page/page.stories.tsx b/src/components/page/page.stories.tsx index cf43aaf3..f4961eec 100644 --- a/src/components/page/page.stories.tsx +++ b/src/components/page/page.stories.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Button, ButtonLink } from "../button"; import { Card } from "../card"; +import { DataGrid } from "../datagrid"; import { Select } from "../form"; import { Outline } from "../icon"; import { Container, Grid } from "../layout"; @@ -90,10 +91,106 @@ export const SamplePage: Story = { > - - - + + {}, + page: 3, + pageSize: 20, + pageSizeOptions: [ + { + label: 10, + }, + { + label: 20, + }, + { + label: 30, + }, + { + label: 40, + }, + { + label: 50, + }, + ], + }} + results={[ + { + Omschrijving: "Afvalpas vervangen", + Versie: 2, + Actief: false, + Concept: true, + Toekomstig: false, + }, + { + Omschrijving: "Erfpacht wijzigen", + Actief: true, + Versie: 4, + Concept: true, + Toekomstig: true, + }, + { + Omschrijving: "Dakkapel vervangen", + Actief: false, + Versie: 1, + Concept: false, + Toekomstig: false, + }, + { + Omschrijving: "Dakkapel vervangen", + Actief: true, + Versie: 4, + Concept: true, + Toekomstig: true, + }, + { + Omschrijving: "Erfpacht wijzigen", + Actief: false, + Versie: 2, + Concept: true, + Toekomstig: false, + }, + { + Omschrijving: "Dakkapel vervangen", + Actief: true, + Versie: 4, + Concept: true, + Toekomstig: true, + }, + { + Omschrijving: "Erfpacht wijzigen", + Actief: false, + Versie: 1, + Concept: false, + Toekomstig: false, + }, + { + Omschrijving: "Dakkapel vervangen", + Versie: 1, + Actief: false, + Concept: false, + Toekomstig: false, + }, + ]} + title="Zaaktypen" + /> + + diff --git a/src/components/paginator/paginator.scss b/src/components/paginator/paginator.scss index df0746cb..27b4372e 100644 --- a/src/components/paginator/paginator.scss +++ b/src/components/paginator/paginator.scss @@ -12,12 +12,16 @@ } @media screen and (max-width: constants.$breakpoint-desktop - 1px) { + &__section--form { + display: none; + } + &__section { width: 100%; + justify-content: flex-end; - > * { - width: 100% !important; - white-space: nowrap; + .mykn-button { + justify-content: center !important; } } } diff --git a/src/components/paginator/paginator.tsx b/src/components/paginator/paginator.tsx index 21590a82..e3cacf30 100644 --- a/src/components/paginator/paginator.tsx +++ b/src/components/paginator/paginator.tsx @@ -7,7 +7,7 @@ import { Outline } from "../icon"; import { P } from "../typography"; import "./paginator.scss"; -export type PaginatorProps = { +export type PaginatorProps = React.HTMLAttributes & { /** The total number of results (items not pages). */ count: number; @@ -35,15 +35,18 @@ export type PaginatorProps = { /** The go to next page (accessible) label. */ labelNext: string; - /** The options for the page size, can be omitted if no variable pages size is supported. */ - pageSizeOptions?: Option[]; - /** * The loading (accessible) label, * @see onPageChange */ labelLoading?: string; + /** Indicates whether the spinner should be shown (requires `labelLoading`). */ + loading?: boolean; + + /** The options for the page size, can be omitted if no variable pages size is supported. */ + pageSizeOptions?: Option[]; + /** * Gets called when the selected page is changed * @@ -89,6 +92,7 @@ export const Paginator: React.FC = ({ labelPrevious = "Go to previous page", labelNext = "Go to next page", labelLoading, + loading = undefined, page = 1, pageSize, pageSizeOptions = [], @@ -182,6 +186,9 @@ export const Paginator: React.FC = ({ setPageState(sanitizedValue); }; + // `loading` takes precedence over `loadingState` (derived from Promise). + const isLoading = typeof loading === "boolean" ? loading : loadingState; + return ( ); diff --git a/src/components/toolbar/toolbar.scss b/src/components/toolbar/toolbar.scss index 7b7a3de9..1916f60d 100644 --- a/src/components/toolbar/toolbar.scss +++ b/src/components/toolbar/toolbar.scss @@ -87,4 +87,9 @@ text-decoration: underline; } } + + &--pad-h { + padding-inline-start: var(--spacing-h-m); + padding-inline-end: var(--spacing-h-m); + } } diff --git a/src/components/toolbar/toolbar.tsx b/src/components/toolbar/toolbar.tsx index 0ff49b9e..fa74cd13 100644 --- a/src/components/toolbar/toolbar.tsx +++ b/src/components/toolbar/toolbar.tsx @@ -18,6 +18,9 @@ export type ToolbarProps = React.PropsWithChildren< /** When set to true, padding is applied to A components to match Button component's height. */ padA?: boolean; + /** When set to true, horizontal padding is applied to the toolbar. */ + padH?: boolean; + /** The variant (style) of the toolbar. */ variant?: "normal" | "transparent"; @@ -33,6 +36,7 @@ export type ToolbarProps = React.PropsWithChildren< * @param align * @param direction * @param padA + * @param padH * @param variant * @param items * @param props @@ -43,6 +47,7 @@ export const Toolbar: React.FC = ({ align = "start", direction = "horizontal", padA = false, + padH = false, variant = "normal", items = [], ...props @@ -92,6 +97,7 @@ export const Toolbar: React.FC = ({ `mykn-toolbar--variant-${variant}`, { "mykn-toolbar--pad-a": padA, + "mykn-toolbar--pad-h": padH, }, )} role="toolbar" diff --git a/src/components/typography/h3/h3.tsx b/src/components/typography/h3/h3.tsx index 206871b7..3f1bddd6 100644 --- a/src/components/typography/h3/h3.tsx +++ b/src/components/typography/h3/h3.tsx @@ -2,9 +2,9 @@ import React from "react"; import "./h3.scss"; -export type H3Props = React.PropsWithChildren<{ - // Props here. -}>; +export type H3Props = React.PropsWithChildren< + React.HTMLAttributes +>; /** * H3 component diff --git a/src/components/typography/p/p.scss b/src/components/typography/p/p.scss index 63ad0565..05350f36 100644 --- a/src/components/typography/p/p.scss +++ b/src/components/typography/p/p.scss @@ -7,12 +7,16 @@ margin-top: 0; margin-bottom: 0; - &--size-xs { - font-size: var(--typography-font-size-body-xs); - line-height: var(--typography-line-height-body-xs); + &--bold { + font-weight: var(--typography-font-weight-bold); } &--muted { color: var(--typography-color-muted); } + + &--size-xs { + font-size: var(--typography-font-size-body-xs); + line-height: var(--typography-line-height-body-xs); + } } diff --git a/src/components/typography/p/p.tsx b/src/components/typography/p/p.tsx index 3fab3378..18beeeae 100644 --- a/src/components/typography/p/p.tsx +++ b/src/components/typography/p/p.tsx @@ -4,6 +4,9 @@ import React from "react"; import "./p.scss"; export type PProps = React.PropsWithChildren<{ + /** Whether the text should be presented bold. */ + bold?: boolean; + /** Whether the text should be presented in a lighter color. */ muted?: boolean; @@ -13,19 +16,23 @@ export type PProps = React.PropsWithChildren<{ /** * Ul component + * @param bold * @param children + * @param muted * @param size * @param props * @constructor */ export const P: React.FC = ({ + bold = false, children, - muted, + muted = false, size = "s", ...props }) => (