Skip to content

Commit

Permalink
feat(OnyxForm): Add successMessage to useCustomValidity composable (#…
Browse files Browse the repository at this point in the history
…2031)

<!-- Is your PR related to an issue? Then please link it via the
"Relates to #" below. Else, remove it. -->

Relates to #573 

Add successMessage prop to useCustomValidity composable. This property
will be provided in OnyxForm component and later injected by it's child
input components to represent the success state.
  • Loading branch information
MajaZarkova authored Nov 4, 2024
1 parent c83c756 commit a544a1e
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts" setup>
import { computed, ref } from "vue";
import { type FormErrorMessages, getCustomErrorText } from "../../composables/useCustomValidity";
import { type FormMessages, getFormMessageText } from "../../composables/useCustomValidity";
import OnyxTooltip from "../OnyxTooltip/OnyxTooltip.vue";
const props = defineProps<{
Expand All @@ -9,7 +9,7 @@ const props = defineProps<{
* errorMessages are provided. Without errorMessages, the
* component will not be rendered inside a slot.
*/
errorMessages?: FormErrorMessages;
errorMessages?: FormMessages;
/** We don't show an error if the content is not interactive */
disabled?: boolean;
}>();
Expand All @@ -21,7 +21,7 @@ defineSlots<{
default(): unknown;
}>();
const tooltipError = computed(() => getCustomErrorText(props.errorMessages));
const tooltipError = computed(() => getFormMessageText(props.errorMessages));
const targetRef = ref<HTMLDivElement>();
</script>
Expand Down
4 changes: 2 additions & 2 deletions packages/sit-onyx/src/components/OnyxFormElement/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RequiredMarkerProp } from "../../composables/required";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";

export type OnyxFormElementProps = RequiredMarkerProp & {
/**
Expand Down Expand Up @@ -40,7 +40,7 @@ export type OnyxFormElementProps = RequiredMarkerProp & {
/**
* Error messages that inform about causes for invalidity of form components
*/
errorMessages?: FormErrorMessages;
errorMessages?: FormMessages;
/**
* Maximum number of characters that are allowed to be entered.
* Warning: when the value is (pre)set programmatically,
Expand Down
4 changes: 2 additions & 2 deletions packages/sit-onyx/src/components/OnyxInput/OnyxInput.ct.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import { createFormElementUtils } from "../OnyxFormElement/OnyxFormElement.ct-utils";
Expand Down Expand Up @@ -108,7 +108,7 @@ test.describe("Screenshot tests", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
4 changes: 2 additions & 2 deletions packages/sit-onyx/src/components/OnyxSelect/OnyxSelect.ct.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { MountResultJsx } from "@playwright/experimental-ct-vue";
import { comboboxSelectOnlyTesting, comboboxTesting } from "@sit-onyx/headless/playwright";
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import {
adjustAbsolutePositionScreenshot,
Expand Down Expand Up @@ -315,7 +315,7 @@ test.describe("Invalidity handling screenshots", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DENSITIES } from "../../composables/density";
import type { FormErrorMessages } from "../../composables/useCustomValidity";
import type { FormMessages } from "../../composables/useCustomValidity";
import { expect, test } from "../../playwright/a11y";
import { executeMatrixScreenshotTest } from "../../playwright/screenshots";
import { createFormElementUtils } from "../OnyxFormElement/OnyxFormElement.ct-utils";
Expand Down Expand Up @@ -100,7 +100,7 @@ test.describe("Screenshot tests", () => {
const message = showLongMessage
? "Very long message that should be truncated"
: "Test message";
const errorMessages: FormErrorMessages = {
const errorMessages: FormMessages = {
shortMessage: showLongMessage
? "Very long error preview that should be truncated"
: "Test error",
Expand Down
53 changes: 33 additions & 20 deletions packages/sit-onyx/src/composables/useCustomValidity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ import type { BaseSelectOption } from "../types";
import { areObjectsFlatEqual } from "../utils/objects";
import { getFirstInvalidType, transformValidityStateToObject } from "../utils/validity";

export type CustomErrorType = string | FormErrorMessages;
export type CustomMessageType = string | FormMessages;

export type CustomValidityProp = {
/**
* Custom error message to show. Will only show up after the user has interacted with the input.
*/
customError?: CustomErrorType;
customError?: CustomMessageType;
/**
* Success message to show. Will only show up after the user has interacted with the input.
*/
successMessage?: CustomMessageType;
};

export type UseCustomValidityOptions = {
Expand Down Expand Up @@ -45,39 +49,38 @@ export const TRANSLATED_INPUT_TYPES = Object.keys(
export type TranslatedInputType = (typeof TRANSLATED_INPUT_TYPES)[number];

/**
* Translated error messages that inform about causes for invalidity of form components
* Translated messages that inform about the validity state of form components
*/
export type FormErrorMessages = {
export type FormMessages = {
/**
* A short error message preview to inform the user about the cause of the error
* A short message preview to inform the user about the validity state
*/
shortMessage: string;
/**
* An extended informative error message to provide more info
* how the error cause can be resolved
* An extended informative message to provide more info
*/
longMessage?: string;
};

/**
* Transforms a customError into the format needed to display an error preview and extended message
* Transforms a customMessage into the format needed to display an error preview and extended message
*/
export const getCustomErrors = (customError?: CustomErrorType): FormErrorMessages | undefined => {
if (!customError) return;
if (typeof customError === "string") {
// we can't guarantee a custom error message will be short,
export const getFormMessages = (customMessage?: CustomMessageType): FormMessages | undefined => {
if (!customMessage) return;
if (typeof customMessage === "string") {
// we can't guarantee a custom message will be short,
// so in case it overflows, by adding it to "longMessage",
// it will still be visible in a tooltip
return { shortMessage: customError, longMessage: customError };
return { shortMessage: customMessage, longMessage: customMessage };
}
return customError;
return customMessage;
};

/**
* Returns a string combining short + long message or just the customError if it was provided as single string.
* Returns a string combining short + long message or just the customMessage if it was provided as single string.
* Will be used e.g. for customInvalidity and showing a tooltip e.g. in RadioButtons
*/
export const getCustomErrorText = (customError?: CustomErrorType): string | undefined => {
export const getFormMessageText = (customError?: CustomMessageType): string | undefined => {
if (!customError) return;
if (typeof customError === "string") {
return customError;
Expand All @@ -90,7 +93,7 @@ export const getCustomErrorText = (customError?: CustomErrorType): string | unde
};

/**
* Composable for unified handling of custom error messages for form components.
* Composable for unified handling of custom messages for form components.
* Will call `setCustomValidity()` accordingly and emit the "validityChange" event
* whenever the input value / error changes.
*
Expand Down Expand Up @@ -128,7 +131,7 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
/**
* Sync custom error with the native input validity.
*/
watchEffect(() => el.setCustomValidity(getCustomErrorText(options.props.customError) ?? ""));
watchEffect(() => el.setCustomValidity(getFormMessageText(options.props.customError) ?? ""));

watch(
// we need to watch all props instead of only modelValue so the validity is re-checked
Expand Down Expand Up @@ -166,11 +169,11 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
},
} satisfies Directive<InputValidationElement, undefined>;

const errorMessages = computed<FormErrorMessages | undefined>(() => {
const errorMessages = computed<FormMessages | undefined>(() => {
if (!validityState.value || validityState.value.valid) return;

const errorType = getFirstInvalidType(validityState.value);
const customErrors = getCustomErrors(options.props.customError);
const customErrors = getFormMessages(options.props.customError);
// a custom error message always is considered first
if (customErrors || errorType === "customError") {
if (!customErrors) return;
Expand Down Expand Up @@ -207,6 +210,12 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
};
});

const successMessages = computed<FormMessages | undefined>(() => {
if (validityState.value === undefined || !validityState.value.valid) return;

return getFormMessages(options.props.successMessage);
});

return {
/**
* Directive to set the custom error message and emit validityChange event.
Expand All @@ -216,5 +225,9 @@ export const useCustomValidity = (options: UseCustomValidityOptions) => {
* A custom error or the default translation of the first invalid state if one exists.
*/
errorMessages,
/**
* A custom success message if provided by the user.
*/
successMessages,
};
};

0 comments on commit a544a1e

Please sign in to comment.