From 4f1cb0970c8572f156d5562372e9146ad00f444b Mon Sep 17 00:00:00 2001 From: Lisa Meyer <84317589+Lisa18289@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:13:04 +0100 Subject: [PATCH] feat(LabeledValue): Introduce component --- .pnp.cjs | 22 ++++++++++ packages/components/.storybook/preview.tsx | 9 ++-- packages/components/package.json | 3 ++ .../src/components/CopyButton/CopyButton.tsx | 42 +++++++++++++++++++ .../src/components/CopyButton/index.ts | 3 ++ .../CopyButton/locales/de-DE.locale.json | 3 ++ .../CopyButton/locales/en-EN.locale.json | 3 ++ .../CopyButton/stories/Default.stories.tsx | 17 ++++++++ .../LabeledValue/LabeledValue.module.scss | 30 +++++++++++++ .../components/LabeledValue/LabeledValue.tsx | 36 ++++++++++++++++ .../src/components/LabeledValue/index.ts | 3 ++ .../LabeledValue/stories/Default.stories.tsx | 35 ++++++++++++++++ .../stories/EdgeCases.stories.tsx | 35 ++++++++++++++++ .../src/components/StatusIcon/StatusIcon.tsx | 6 +-- .../Tooltip/stories/Default.stories.tsx | 15 ++++--- .../Tooltip/stories/EdgeCases.stories.tsx | 13 +++--- packages/components/vite.build.config.ts | 2 + .../src/components/labeled-value.yml | 5 +++ yarn.lock | 17 ++++++++ 19 files changed, 280 insertions(+), 19 deletions(-) create mode 100644 packages/components/src/components/CopyButton/CopyButton.tsx create mode 100644 packages/components/src/components/CopyButton/index.ts create mode 100644 packages/components/src/components/CopyButton/locales/de-DE.locale.json create mode 100644 packages/components/src/components/CopyButton/locales/en-EN.locale.json create mode 100644 packages/components/src/components/CopyButton/stories/Default.stories.tsx create mode 100644 packages/components/src/components/LabeledValue/LabeledValue.module.scss create mode 100644 packages/components/src/components/LabeledValue/LabeledValue.tsx create mode 100644 packages/components/src/components/LabeledValue/index.ts create mode 100644 packages/components/src/components/LabeledValue/stories/Default.stories.tsx create mode 100644 packages/components/src/components/LabeledValue/stories/EdgeCases.stories.tsx create mode 100644 packages/design-tokens/src/components/labeled-value.yml diff --git a/.pnp.cjs b/.pnp.cjs index 7dcd5c20d..3dcac0aa2 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6122,6 +6122,7 @@ const RAW_RUNTIME_STATE = ["@types/rollup", "npm:0.54.0"],\ ["clsx", "npm:2.1.0"],\ ["concurrently", "npm:8.2.2"],\ + ["copy-to-clipboard", "npm:3.3.3"],\ ["decamelize", "npm:6.0.0"],\ ["html-react-parser", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:5.1.1"],\ ["jest", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:29.7.0"],\ @@ -6187,6 +6188,7 @@ const RAW_RUNTIME_STATE = ["@types/rollup", "npm:0.54.0"],\ ["clsx", "npm:2.1.0"],\ ["concurrently", "npm:8.2.2"],\ + ["copy-to-clipboard", "npm:3.3.3"],\ ["decamelize", "npm:6.0.0"],\ ["html-react-parser", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:5.1.1"],\ ["jest", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:29.7.0"],\ @@ -6258,6 +6260,7 @@ const RAW_RUNTIME_STATE = ["@types/rollup", "npm:0.54.0"],\ ["clsx", "npm:2.1.0"],\ ["concurrently", "npm:8.2.2"],\ + ["copy-to-clipboard", "npm:3.3.3"],\ ["decamelize", "npm:6.0.0"],\ ["html-react-parser", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:5.1.1"],\ ["jest", "virtual:160c7a696a99b065a822859af8c18a938041a659df75ba6216e289a2ba885865ddea0afdd5ed1924ae3f379fc9ec188b83f6e71e756a0bac8e9ca356654a6a90#npm:29.7.0"],\ @@ -17753,6 +17756,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["copy-to-clipboard", [\ + ["npm:3.3.3", {\ + "packageLocation": "./.yarn/cache/copy-to-clipboard-npm-3.3.3-6964e6cfad-3ebf5e8ee0.zip/node_modules/copy-to-clipboard/",\ + "packageDependencies": [\ + ["copy-to-clipboard", "npm:3.3.3"],\ + ["toggle-selection", "npm:1.0.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["core-js-compat", [\ ["npm:3.35.1", {\ "packageLocation": "./.yarn/cache/core-js-compat-npm-3.35.1-1088e0320e-c3b872e1f9.zip/node_modules/core-js-compat/",\ @@ -30108,6 +30121,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["toggle-selection", [\ + ["npm:1.0.6", {\ + "packageLocation": "./.yarn/cache/toggle-selection-npm-1.0.6-c506b73005-f2cf1f2c70.zip/node_modules/toggle-selection/",\ + "packageDependencies": [\ + ["toggle-selection", "npm:1.0.6"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["toidentifier", [\ ["npm:1.0.1", {\ "packageLocation": "./.yarn/cache/toidentifier-npm-1.0.1-f759712599-9393727993.zip/node_modules/toidentifier/",\ diff --git a/packages/components/.storybook/preview.tsx b/packages/components/.storybook/preview.tsx index e8ffd89db..744740059 100644 --- a/packages/components/.storybook/preview.tsx +++ b/packages/components/.storybook/preview.tsx @@ -3,11 +3,10 @@ import React from "react"; const preview: Preview = { decorators: [ - (Story) => ( -
- -
- ), + (Story) => { + document.body.classList.add("flow"); + return ; + }, ], globalTypes: { rtlDirection: {}, diff --git a/packages/components/package.json b/packages/components/package.json index 8fba27d54..389978ca4 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -8,6 +8,7 @@ "./Button": "./dist/Button.js", "./Checkbox": "./dist/Checkbox.js", "./Content": "./dist/Content.js", + "./CopyButton": "./dist/CopyButton.js", "./FieldDescription": "./dist/FieldDescription.js", "./FieldError": "./dist/FieldError.js", "./Heading": "./dist/Heading.js", @@ -15,6 +16,7 @@ "./Image": "./dist/Image.js", "./Initials": "./dist/Initials.js", "./Label": "./dist/Label.js", + "./LabeledValue": "./dist/LabeledValue.js", "./Link": "./dist/Link.js", "./Navigation": "./dist/Navigation.js", "./Note": "./dist/Note.js", @@ -48,6 +50,7 @@ "@react-aria/utils": "^3.23.0", "@react-types/shared": "^3.22.0", "clsx": "^2.1.0", + "copy-to-clipboard": "^3.3.3", "html-react-parser": "^5.1.1", "react-aria": "^3.31.1", "react-aria-components": "^1.0.1", diff --git a/packages/components/src/components/CopyButton/CopyButton.tsx b/packages/components/src/components/CopyButton/CopyButton.tsx new file mode 100644 index 000000000..3136f2dba --- /dev/null +++ b/packages/components/src/components/CopyButton/CopyButton.tsx @@ -0,0 +1,42 @@ +import React, { FC } from "react"; +import copy from "copy-to-clipboard"; +import { Button } from "@/components/Button"; +import { Icon } from "@/components/Icon"; +import { faCopy } from "@fortawesome/free-regular-svg-icons/faCopy"; +import locales from "./locales/*.locale.json"; +import { useLocalizedStringFormatter } from "react-aria"; +import { Tooltip, TooltipTrigger } from "@/components/Tooltip"; + +export interface CopyButtonProps { + value: string; + className?: string; +} + +export const CopyButton: FC = (props) => { + const { value, className } = props; + + const stringFormatter = useLocalizedStringFormatter(locales); + + const tooltip = stringFormatter.format("copyButton.copy"); + + const copyValue = () => { + copy(value); + }; + + return ( + + + {tooltip} + + ); +}; + +export default CopyButton; diff --git a/packages/components/src/components/CopyButton/index.ts b/packages/components/src/components/CopyButton/index.ts new file mode 100644 index 000000000..92738b743 --- /dev/null +++ b/packages/components/src/components/CopyButton/index.ts @@ -0,0 +1,3 @@ +import { CopyButton } from "./CopyButton"; +export { type CopyButtonProps, CopyButton } from "./CopyButton"; +export default CopyButton; diff --git a/packages/components/src/components/CopyButton/locales/de-DE.locale.json b/packages/components/src/components/CopyButton/locales/de-DE.locale.json new file mode 100644 index 000000000..b1b6b4965 --- /dev/null +++ b/packages/components/src/components/CopyButton/locales/de-DE.locale.json @@ -0,0 +1,3 @@ +{ + "copyButton.copy": "Kopieren" +} diff --git a/packages/components/src/components/CopyButton/locales/en-EN.locale.json b/packages/components/src/components/CopyButton/locales/en-EN.locale.json new file mode 100644 index 000000000..d77bca104 --- /dev/null +++ b/packages/components/src/components/CopyButton/locales/en-EN.locale.json @@ -0,0 +1,3 @@ +{ + "copyButton.copy": "Copy" +} diff --git a/packages/components/src/components/CopyButton/stories/Default.stories.tsx b/packages/components/src/components/CopyButton/stories/Default.stories.tsx new file mode 100644 index 000000000..df56fdde3 --- /dev/null +++ b/packages/components/src/components/CopyButton/stories/Default.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; +import { CopyButton } from "../CopyButton"; + +const meta: Meta = { + title: "Buttons/CopyButton", + component: CopyButton, + render: (props) => , + parameters: { + controls: { exclude: ["value", "className"] }, + }, +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/components/src/components/LabeledValue/LabeledValue.module.scss b/packages/components/src/components/LabeledValue/LabeledValue.module.scss new file mode 100644 index 000000000..17db89cfa --- /dev/null +++ b/packages/components/src/components/LabeledValue/LabeledValue.module.scss @@ -0,0 +1,30 @@ +@import "@/styles"; + +.labeledValue { + display: grid; + grid-template-areas: + "label" + "value"; + row-gap: var(--labeled-value--label-to-value-spacing); + column-gap: var(--labeled-value--value-to-button-spacing); + grid-template-columns: auto 1fr; + + &:has(.button) { + grid-template-areas: + "label label" + "value button"; + } +} + +.label { + grid-area: label; +} + +.content { + grid-area: value; +} + +.button { + grid-area: button; + justify-self: start; +} diff --git a/packages/components/src/components/LabeledValue/LabeledValue.tsx b/packages/components/src/components/LabeledValue/LabeledValue.tsx new file mode 100644 index 000000000..aea79cab6 --- /dev/null +++ b/packages/components/src/components/LabeledValue/LabeledValue.tsx @@ -0,0 +1,36 @@ +import React, { FC, PropsWithChildren } from "react"; +import styles from "./LabeledValue.module.scss"; +import clsx from "clsx"; +import { PropsContext, PropsContextProvider } from "@/lib/propsContext"; + +export interface LabeledValueProps extends PropsWithChildren { + className?: string; +} + +export const LabeledValue: FC = (props) => { + const { children, className } = props; + + const rootClassName = clsx(styles.labeledValue, className); + + const propsContext: PropsContext = { + Label: { + className: styles.label, + }, + Content: { + className: styles.content, + }, + Button: { + className: styles.button, + }, + }; + + return ( +
+ + {children} + +
+ ); +}; + +export default LabeledValue; diff --git a/packages/components/src/components/LabeledValue/index.ts b/packages/components/src/components/LabeledValue/index.ts new file mode 100644 index 000000000..668be3b3d --- /dev/null +++ b/packages/components/src/components/LabeledValue/index.ts @@ -0,0 +1,3 @@ +import { LabeledValue } from "./LabeledValue"; +export { type LabeledValueProps, LabeledValue } from "./LabeledValue"; +export default LabeledValue; diff --git a/packages/components/src/components/LabeledValue/stories/Default.stories.tsx b/packages/components/src/components/LabeledValue/stories/Default.stories.tsx new file mode 100644 index 000000000..9c2146ef9 --- /dev/null +++ b/packages/components/src/components/LabeledValue/stories/Default.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import LabeledValue from "../LabeledValue"; +import React from "react"; +import { Label } from "@/components/Label"; +import { Content } from "@/components/Content"; +import { CopyButton } from "@/components/CopyButton"; + +const meta: Meta = { + title: "Content/Labeled Value", + component: LabeledValue, + parameters: { + controls: { exclude: ["className"] }, + }, + render: (props) => ( + + + My proSpace + + ), +}; +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const WithCopyButton: Story = { + render: (props) => ( + + + My proSpace + + + ), +}; diff --git a/packages/components/src/components/LabeledValue/stories/EdgeCases.stories.tsx b/packages/components/src/components/LabeledValue/stories/EdgeCases.stories.tsx new file mode 100644 index 000000000..1d4f7dfd0 --- /dev/null +++ b/packages/components/src/components/LabeledValue/stories/EdgeCases.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import LabeledValue from "../LabeledValue"; +import defaultMeta from "./Default.stories"; +import { dummyText } from "@/lib/dev/dummyText"; +import { Label } from "@/components/Label"; +import { Content } from "@/components/Content"; +import React from "react"; +import { CopyButton } from "@/components/CopyButton"; + +const meta: Meta = { + title: "Content/Labeled Value/Edge Cases", + ...defaultMeta, +}; +export default meta; + +type Story = StoryObj; + +export const LongLabel: Story = { + render: (props) => ( + + + {dummyText.short} + + + ), +}; +export const LongContent: Story = { + render: (props) => ( + + + {dummyText.long} + + + ), +}; diff --git a/packages/components/src/components/StatusIcon/StatusIcon.tsx b/packages/components/src/components/StatusIcon/StatusIcon.tsx index 1ebd828c4..768904f7c 100644 --- a/packages/components/src/components/StatusIcon/StatusIcon.tsx +++ b/packages/components/src/components/StatusIcon/StatusIcon.tsx @@ -14,9 +14,9 @@ export interface StatusIconProps extends StatusVariantProps { export const StatusIcon: FC = (props) => { const { variant = "info", ...rest } = props; - const ariaLabel = useLocalizedStringFormatter(locales).format( - `statusIcon.${variant}`, - ); + const stringFormatter = useLocalizedStringFormatter(locales); + + const ariaLabel = stringFormatter.format(`statusIcon.${variant}`); const icon = variant === "info" diff --git a/packages/components/src/components/Tooltip/stories/Default.stories.tsx b/packages/components/src/components/Tooltip/stories/Default.stories.tsx index bad41a996..6ed6d552a 100644 --- a/packages/components/src/components/Tooltip/stories/Default.stories.tsx +++ b/packages/components/src/components/Tooltip/stories/Default.stories.tsx @@ -4,17 +4,20 @@ import React from "react"; import { Text } from "@/components/Text"; import { Button } from "@/components/Button"; import { Icon } from "@/components/Icon"; -import { faCopy } from "@fortawesome/free-regular-svg-icons/faCopy"; +import { faSave } from "@fortawesome/free-regular-svg-icons/faSave"; const meta: Meta = { title: "Overlays/Tooltip", component: Tooltip, - render: () => ( - - - Copy + Save ), }; diff --git a/packages/components/src/components/Tooltip/stories/EdgeCases.stories.tsx b/packages/components/src/components/Tooltip/stories/EdgeCases.stories.tsx index c441aa892..70aff0c2c 100644 --- a/packages/components/src/components/Tooltip/stories/EdgeCases.stories.tsx +++ b/packages/components/src/components/Tooltip/stories/EdgeCases.stories.tsx @@ -4,17 +4,20 @@ import React from "react"; import { Text } from "@/components/Text"; import { Button } from "@/components/Button"; import { Icon } from "@/components/Icon"; -import { faCopy } from "@fortawesome/free-regular-svg-icons/faCopy"; import defaultMeta from "./Default.stories"; import { dummyText } from "@/lib/dev/dummyText"; +import { faSave } from "@fortawesome/free-regular-svg-icons/faSave"; const meta: Meta = { ...defaultMeta, title: "Overlays/Tooltip/Edge Cases", - render: () => ( - - {dummyText.medium} diff --git a/packages/components/vite.build.config.ts b/packages/components/vite.build.config.ts index 10904d701..fa34ad04b 100644 --- a/packages/components/vite.build.config.ts +++ b/packages/components/vite.build.config.ts @@ -19,6 +19,7 @@ export default defineConfig( Button: "./src/components/Button/index.ts", Checkbox: "./src/components/Checkbox/index.ts", Content: "./src/components/Content/index.ts", + CopyButton: "./src/components/CopyButton/index.ts", FieldDescription: "./src/components/FieldDescription/index.ts", FieldError: "./src/components/FieldError/index.ts", Heading: "./src/components/Heading/index.ts", @@ -26,6 +27,7 @@ export default defineConfig( Image: "./src/components/Image/index.ts", Initials: "./src/components/Initials/index.ts", Label: "./src/components/Label/index.ts", + LabeledValue: "./src/components/LabeledValue/index.ts", Link: "./src/components/Link/index.ts", Navigation: "./src/components/Navigation/index.ts", Note: "./src/components/Note/index.ts", diff --git a/packages/design-tokens/src/components/labeled-value.yml b/packages/design-tokens/src/components/labeled-value.yml new file mode 100644 index 000000000..963df2e91 --- /dev/null +++ b/packages/design-tokens/src/components/labeled-value.yml @@ -0,0 +1,5 @@ +labeled-value: + label-to-value-spacing: + value: "{size-rem.xs}" + value-to-button-spacing: + value: "{size-rem.xs}" diff --git a/yarn.lock b/yarn.lock index d30a682f9..c11c87e8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3214,6 +3214,7 @@ __metadata: "@types/rollup": "npm:^0.54.0" clsx: "npm:^2.1.0" concurrently: "npm:^8.2.2" + copy-to-clipboard: "npm:^3.3.3" decamelize: "npm:^6.0.0" html-react-parser: "npm:^5.1.1" jest: "npm:^29.7.0" @@ -10750,6 +10751,15 @@ __metadata: languageName: node linkType: hard +"copy-to-clipboard@npm:^3.3.3": + version: 3.3.3 + resolution: "copy-to-clipboard@npm:3.3.3" + dependencies: + toggle-selection: "npm:^1.0.6" + checksum: 3ebf5e8ee00601f8c440b83ec08d838e8eabb068c1fae94a9cda6b42f288f7e1b552f3463635f419af44bf7675afc8d0390d30876cf5c2d5d35f86d9c56a3e5f + languageName: node + linkType: hard + "core-js-compat@npm:^3.31.0, core-js-compat@npm:^3.33.1": version: 3.35.1 resolution: "core-js-compat@npm:3.35.1" @@ -21568,6 +21578,13 @@ __metadata: languageName: node linkType: hard +"toggle-selection@npm:^1.0.6": + version: 1.0.6 + resolution: "toggle-selection@npm:1.0.6" + checksum: f2cf1f2c70f374fd87b0cdc8007453ba9e981c4305a8bf4eac10a30e62ecdfd28bca7d18f8f15b15a506bf8a7bfb20dbe3539f0fcf2a2c8396c1a78d53e1f179 + languageName: node + linkType: hard + "toidentifier@npm:1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1"