diff --git a/playwright/visual.test.ts-snapshots/Form-Controls-EditInPlace-With-Error-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/Form-Controls-EditInPlace-With-Error-1-chromium-linux.png index c5ad4de4..ac2e286a 100644 Binary files a/playwright/visual.test.ts-snapshots/Form-Controls-EditInPlace-With-Error-1-chromium-linux.png and b/playwright/visual.test.ts-snapshots/Form-Controls-EditInPlace-With-Error-1-chromium-linux.png differ diff --git a/playwright/visual.test.ts-snapshots/InlineSpinner-Default-1-chromium-linux.png b/playwright/visual.test.ts-snapshots/InlineSpinner-Default-1-chromium-linux.png new file mode 100644 index 00000000..9e763573 Binary files /dev/null and b/playwright/visual.test.ts-snapshots/InlineSpinner-Default-1-chromium-linux.png differ diff --git a/src/components/Form/Controls/EditInPlace/EditInPlace.module.css b/src/components/Form/Controls/EditInPlace/EditInPlace.module.css index a48b526b..c893a44f 100644 --- a/src/components/Form/Controls/EditInPlace/EditInPlace.module.css +++ b/src/components/Form/Controls/EditInPlace/EditInPlace.module.css @@ -91,14 +91,15 @@ limitations under the License. .caption-line { margin-block-start: var(--cpd-space-2x); + display: flex; + align-items: center; + gap: var(--cpd-space-2x); } .caption-icon { display: inline-block; - vertical-align: middle; inline-size: 20px; block-size: 20px; - margin-inline-end: var(--cpd-space-2x); } .caption-icon-error { @@ -107,18 +108,28 @@ limitations under the License. .caption-icon-saved { background-color: var(--cpd-color-icon-success-primary); + + /* + * We are trying to match the size of the error icon which + * is displayed 20px big but has 2px of border in an SVG + * with a viewBox of 24x24... + */ + inline-size: calc(20px * (20 / 24)); + block-size: calc(20px * (20 / 24)); + margin: calc(2px * (20 / 24)); border-radius: 20px; text-align: center; - margin-inline-end: var(--cpd-space-2x); svg { color: var(--cpd-color-icon-on-solid-primary); + position: relative; + inset-block-start: -1px; } } .caption-text { - vertical-align: middle; - font-size: 13px; + font-size: var(--cpd-font-body-sm-medium); + font-weight: var(--cpd-font-weight-medium); } .caption-text-error { @@ -128,3 +139,7 @@ limitations under the License. .caption-text-saved { color: var(--cpd-color-text-success-primary); } + +.caption-text-saving { + color: var(--cpd-color-text-secondary); +} diff --git a/src/components/Form/Controls/EditInPlace/EditInPlace.stories.tsx b/src/components/Form/Controls/EditInPlace/EditInPlace.stories.tsx index 89660a35..1c226ff6 100644 --- a/src/components/Form/Controls/EditInPlace/EditInPlace.stories.tsx +++ b/src/components/Form/Controls/EditInPlace/EditInPlace.stories.tsx @@ -73,6 +73,9 @@ export default { saveButtonLabel: { type: "string", }, + savingLabel: { + type: "string", + }, cancelButtonLabel: { type: "string", }, @@ -83,6 +86,7 @@ export default { value: "", saveButtonLabel: "Save", cancelButtonLabel: "Cancel", + savingLabel: "Saving...", }, } satisfies Meta; diff --git a/src/components/Form/Controls/EditInPlace/EditInPlace.test.tsx b/src/components/Form/Controls/EditInPlace/EditInPlace.test.tsx index 13a63461..48a29267 100644 --- a/src/components/Form/Controls/EditInPlace/EditInPlace.test.tsx +++ b/src/components/Form/Controls/EditInPlace/EditInPlace.test.tsx @@ -48,6 +48,7 @@ describe("EditInPlace", () => { cancelButtonLabel="Cancel" savedLabel={"Saved"} disableSaveButton={props.disableSaveButton} + savingLabel="Saving..." /> diff --git a/src/components/Form/Controls/EditInPlace/EditInPlace.tsx b/src/components/Form/Controls/EditInPlace/EditInPlace.tsx index b568f484..134a44fa 100644 --- a/src/components/Form/Controls/EditInPlace/EditInPlace.tsx +++ b/src/components/Form/Controls/EditInPlace/EditInPlace.tsx @@ -23,6 +23,7 @@ import useId from "../../../../utils/useId"; import CheckIcon from "@vector-im/compound-design-tokens/icons/check.svg"; import CancelIcon from "@vector-im/compound-design-tokens/icons/close.svg"; import ErrorIcon from "@vector-im/compound-design-tokens/icons/error.svg"; +import { InlineSpinner } from "../../../InlineSpinner/InlineSpinner"; type Props = { /** @@ -67,6 +68,11 @@ type Props = { */ saveButtonLabel: string; + /** + * The label for the 'in progress' saving caption + */ + savingLabel: string; + /** * True to disable the save button, false to enable. * Default: false (enabled) @@ -96,6 +102,7 @@ export const EditInPlace = forwardRef( disableSaveButton, error, savedLabel, + savingLabel, ...props }, ref, @@ -211,6 +218,19 @@ export const EditInPlace = forwardRef( )} + {saving && ( +
+ + + {savingLabel} + +
+ )} {savedLabel && showSaved && (
; + +export const Default = { + args: {}, + parameters: {}, +}; diff --git a/src/components/InlineSpinner/InlineSpinner.test.tsx b/src/components/InlineSpinner/InlineSpinner.test.tsx new file mode 100644 index 00000000..e8993ca3 --- /dev/null +++ b/src/components/InlineSpinner/InlineSpinner.test.tsx @@ -0,0 +1,28 @@ +/* +Copyright 2024 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { describe, it, expect } from "vitest"; +import { render } from "@testing-library/react"; +import React from "react"; + +import { InlineSpinner } from "./InlineSpinner"; + +describe("InlineSpinner", () => { + it("renders", () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/components/InlineSpinner/InlineSpinner.tsx b/src/components/InlineSpinner/InlineSpinner.tsx new file mode 100644 index 00000000..6ce4f132 --- /dev/null +++ b/src/components/InlineSpinner/InlineSpinner.tsx @@ -0,0 +1,34 @@ +/* +Copyright 2024 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React, { forwardRef } from "react"; +import styles from "./InlineSpinner.module.css"; +import SpinnerIcon from "@vector-im/compound-design-tokens/icons/spinner.svg"; + +type InlineSpinnerProps = { + size?: number; +} & React.HTMLAttributes; + +export const InlineSpinner = forwardRef( + function InlineSpinner({ size = 20 }: InlineSpinnerProps) { + return ( + + ); + }, +); diff --git a/src/components/InlineSpinner/__snapshots__/InlineSpinner.test.tsx.snap b/src/components/InlineSpinner/__snapshots__/InlineSpinner.test.tsx.snap new file mode 100644 index 00000000..82eb91f7 --- /dev/null +++ b/src/components/InlineSpinner/__snapshots__/InlineSpinner.test.tsx.snap @@ -0,0 +1,21 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`InlineSpinner > renders 1`] = ` + + + + + +`;