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

feat: add input hyperlink #4631

Merged
merged 13 commits into from
Jan 27, 2025
15 changes: 13 additions & 2 deletions apps/tailwind-components/components/form/FieldInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function validate(value: columnValue) {
@focus="$emit('focus')"
@update:modelValue="$emit('update:modelValue', $event)"
@error="$emit('error', $event)"
></LazyInputString>
/>
<LazyInputTextArea
v-else-if="type === 'TEXT'"
ref="input"
Expand All @@ -62,6 +62,17 @@ function validate(value: columnValue) {
@focus="$emit('focus')"
@update:modelValue="$emit('update:modelValue', $event)"
@error="$emit('error', $event)"
></LazyInputTextArea>
/>
<LazyInputHyperlink
v-else-if="type === 'HYPERLINK'"
ref="input"
:id="id"
:label="label"
:required="required"
:value="data as string"
@focus="$emit('focus')"
@update:modelValue="$emit('update:modelValue', $event)"
@error="$emit('error', $event)"
/>
<LazyInputPlaceHolder v-else ref="input" :type="type" />
</template>
61 changes: 61 additions & 0 deletions apps/tailwind-components/components/input/Hyperlink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<template>
<InputString
ref="inputString"
:id="id"
:label="label"
:placeholder="placeholder"
:valid="valid"
:hasError="hasError"
:required="required"
:disabled="disabled"
:value="modelValue"
@update:modelValue="validateInput"
@focus="$emit('focus')"
@blur="$emit('blur')"
/>
</template>

<script setup lang="ts">
import type { InputString } from "#build/components";

const HYPERLINK_REGEX =
/^((https?):\/\/)(www.)?[-a-zA-Z0-9@:%._\\+~#?&//=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%._\\+~#?&//=()]*)\/?$|^$/;

const inputString = ref<InstanceType<typeof InputString>>();

withDefaults(
defineProps<{
id: string;
modelValue?: string;
label?: string;
placeholder?: string;
disabled?: boolean;
required?: boolean;
hasError?: boolean;
valid?: boolean;
}>(),
{
disabled: false,
required: false,
hasError: false,
}
);

const emit = defineEmits(["update:modelValue", "error", "focus", "blur"]);

defineExpose({ validate });

function validateInput(value: string) {
emit("update:modelValue", value);
validate(value);
}

function validate(value: string) {
const stringErrors = inputString.value?.validate(value) || [];
if (HYPERLINK_REGEX.test(value)) {
emit("error", stringErrors);
} else {
emit("error", [{ message: "Invalid hyperlink" }, ...stringErrors]);
}
}
</script>
7 changes: 5 additions & 2 deletions apps/tailwind-components/components/input/String.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ defineExpose({ validate });

function validate(value: columnValue) {
if (props.required && value === "") {
emit("error", [
const errors = [
{ message: `${props.label || props.id} required to complete the form` },
]);
];
emit("error", errors);
return errors;
} else {
emit("error", []);
return [];
}
}

Expand Down
1 change: 0 additions & 1 deletion apps/tailwind-components/pages/Form.story.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ const formFields = ref<InstanceType<typeof FormFields>>();
const formValues = ref<Record<string, columnValue>>({});

function onModelUpdate(value: Record<string, columnValue>) {
console.log("story update", value);
formValues.value = value;
}

Expand Down
41 changes: 41 additions & 0 deletions apps/tailwind-components/pages/input/Hyperlink.story.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<h2>Hyperlink component</h2>
<p>
The <code>Hyperlink Component</code> enables you to use the String component
with the added validation of the input being a hyperlink.
</p>

<div
class="grid grid-cols-2 gap-6 my-5 [&>div]:bg-white [&>div]:p-4 [&_h3]:font-semibold [&_h3]:my-2"
>
<div>
<h3>Input Hyperlink: (model value: {{ demoValue }})</h3>
<InputLabel for="input-hyperlink">
{{ label }}
</InputLabel>
<InputHyperlink
id="input-hyperlink"
v-model="demoValue"
ref="input-hyperlink"
:placeholder="placeholder"
:hasError="error.length > 0"
@error="handleError"
/>
<div>Error: {{ error[0]?.message }}</div>
</div>
</div>
</template>

<script setup lang="ts">
import type { InputHyperlink } from "#build/components";
import type { IFieldError } from "../../../metadata-utils/src/types";

const label = "Input a hyperlink";
const placeholder = "https://example.com";
const demoValue = ref("");
const error = ref<IFieldError[]>([]);

function handleError(errors: IFieldError[]) {
error.value = errors;
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { expect, test } from "@playwright/test";
import playwrightConfig from "../../../playwright.config";
const route = playwrightConfig?.use?.baseURL?.startsWith("http://localhost")
? ""
: "/apps/tailwind-components/#/";

test("the inputHyperLink", async ({ page }) => {
await page.goto(`${route}/input/Hyperlink.story`);
await page.getByRole("button", { name: "focus" }).click({ delay: 500 }); // wait for hydration to complete
await expect(page.getByText("Error:")).not.toContainText(
"Error: Invalid hyperlink"
);
await page.fill("#input-hyperlink", "blaat");
await expect(page.getByText("Error: Invalid hyperlink")).toContainText(
"Error: Invalid hyperlink"
);
await page.getByPlaceholder("https://example.com").clear();
await page.fill("#input-hyperlink", "https://molgenis.net");
await expect(page.getByText("Error:")).not.toContainText(
"Error: Invalid hyperlink"
);
});