diff --git a/apps/metadata-utils/src/types.ts b/apps/metadata-utils/src/types.ts index c36a0d9be9..2fb8b65481 100644 --- a/apps/metadata-utils/src/types.ts +++ b/apps/metadata-utils/src/types.ts @@ -102,7 +102,7 @@ export interface IFormLegendSection { export type columnId = string; export type columnValue = string | number | boolean | null | columnValueObject; -interface columnValueObject { +export interface columnValueObject { [x: string]: columnValue; } diff --git a/apps/tailwind-components/assets/css/main.css b/apps/tailwind-components/assets/css/main.css index c423815896..fbbd8b4d46 100644 --- a/apps/tailwind-components/assets/css/main.css +++ b/apps/tailwind-components/assets/css/main.css @@ -108,6 +108,7 @@ --text-color-search-button-hover: var(--text-blue-800); --text-color-favorite: var(--color-blue-800); --text-color-favorite-hover: var(--color-blue-800); + --text-color-button-text: var(--color-blue-800); --text-color-search-button: var(--color-blue-500); --text-color-search-button-hover: var(--color-blue-800); --text-color-search-results-view-tabs: var(--color-blue-500); diff --git a/apps/tailwind-components/assets/css/theme/aumc.css b/apps/tailwind-components/assets/css/theme/aumc.css index 50b6ad3994..f952dc2dbf 100644 --- a/apps/tailwind-components/assets/css/theme/aumc.css +++ b/apps/tailwind-components/assets/css/theme/aumc.css @@ -24,6 +24,7 @@ --text-color-button-primary: var(--color-white); --text-color-button-primary-hover: var(--color-black); + --text-color-button-text: var(--color-white); --text-color-title: var(--color-white); --text-color-form-header: var(--text-color-title); --text-color-breadcrumb: var(--text-color-title); diff --git a/apps/tailwind-components/assets/css/theme/dark.css b/apps/tailwind-components/assets/css/theme/dark.css index 332e0038bf..dfe436a88f 100644 --- a/apps/tailwind-components/assets/css/theme/dark.css +++ b/apps/tailwind-components/assets/css/theme/dark.css @@ -13,6 +13,7 @@ --text-color-button-primary-hover: var(--color-gray-800); --text-color-button-secondary: var(--color-green-500); --text-color-button-secondary-hover: var(--color-green-200); + --text-color-button-text: var(--color-green-500); --text-color-title: var(--color-white); --text-color-title-contrast: var(--color-blue-500); --text-color-form-header: var(--color-white); diff --git a/apps/tailwind-components/assets/css/theme/molgenis.css b/apps/tailwind-components/assets/css/theme/molgenis.css index 74972b3972..0adca3e868 100644 --- a/apps/tailwind-components/assets/css/theme/molgenis.css +++ b/apps/tailwind-components/assets/css/theme/molgenis.css @@ -48,6 +48,7 @@ --text-color-button-outline-hover: var(--color-blue-700); --text-color-button-disabled: var(--color-gray-600); --text-color-button-disabled-hover: var(--color-gray-600); + --text-color-button-text: var(--color-white); --text-color-menu: var(--color-white); --text-color-sub-menu: var(--color-blue-500); --text-color-sub-menu-hover: var(--color-blue-700); diff --git a/apps/tailwind-components/components/Button.vue b/apps/tailwind-components/components/Button.vue index 2ade2fb506..f026704ed8 100644 --- a/apps/tailwind-components/components/Button.vue +++ b/apps/tailwind-components/components/Button.vue @@ -53,6 +53,12 @@ const SIZE_MAPPING = { medium: "h-14 px-7.5 text-heading-xl gap-4", large: "h-18 px-8.75 text-heading-xl gap-5", }; +const ICON_SIZE_MAPPING = { + tiny: 12, + small: 18, + medium: 24, + large: 36, +}; const ICON_POSITION_MAPPING = { left: "", @@ -71,6 +77,9 @@ const iconPositionClass = computed(() => { return ICON_POSITION_MAPPING[props.iconPosition]; }); +const iconSize = computed(() => { + return ICON_SIZE_MAPPING[props.size]; +}); const tooltipText = computed(() => { return props.tooltip || props.iconOnly ? props.label : ""; }); @@ -82,8 +91,7 @@ const tooltipText = computed(() => { class="flex items-center border rounded-input group-[.button-bar]:rounded-none group-[.button-bar]:first:rounded-l-input group-[.button-bar]:last:rounded-r-input" :class="`${colorClasses} ${sizeClasses} ${iconPositionClass} transition-colors`" > - - + {{ label }} diff --git a/apps/tailwind-components/components/button/Text.vue b/apps/tailwind-components/components/button/Text.vue new file mode 100644 index 0000000000..9a6dbee3ef --- /dev/null +++ b/apps/tailwind-components/components/button/Text.vue @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/apps/tailwind-components/components/form/Field.vue b/apps/tailwind-components/components/form/Field.vue index 5f2dacf3cc..fcc42b2e9e 100644 --- a/apps/tailwind-components/components/form/Field.vue +++ b/apps/tailwind-components/components/form/Field.vue @@ -7,6 +7,7 @@ import type { } from "../../../metadata-utils/src/types"; const props = defineProps<{ + schemaId: string; column: IColumn; data: columnValue; errors: IFieldError[]; @@ -55,6 +56,9 @@ function validate(value: columnValue) { :type="column.columnType" :id="column.id" :label="column.label" + :refSchemaId="column.refSchemaId || schemaId" + :refTableId="column.refTableId" + :refLabel="column.refLabel || column.refLabelDefault" :data="data" :required="!!column.required" :aria-invalid="hasError" diff --git a/apps/tailwind-components/components/form/FieldInput.vue b/apps/tailwind-components/components/form/FieldInput.vue index 662a15a3fc..7572484435 100644 --- a/apps/tailwind-components/components/form/FieldInput.vue +++ b/apps/tailwind-components/components/form/FieldInput.vue @@ -2,17 +2,20 @@ import type { InputString, InputTextArea, + InputRef, InputPlaceHolder, } from "#build/components"; import type { columnId, columnValue, CellValueType, + columnValueObject, } from "../../../metadata-utils/src/types"; type inputComponent = | InstanceType | InstanceType + | InstanceType | InstanceType; defineProps<{ @@ -21,6 +24,9 @@ defineProps<{ label: string; required: boolean; data: columnValue; + refSchemaId?: string; + refTableId?: string; + refLabel?: string; }>(); defineEmits(["focus", "error", "update:modelValue"]); @@ -85,5 +91,16 @@ function validate(value: columnValue) { @update:modelValue="$emit('update:modelValue', $event)" @error="$emit('error', $event)" /> + + diff --git a/apps/tailwind-components/components/form/Fields.vue b/apps/tailwind-components/components/form/Fields.vue index cf3c75cb70..b33dbdc0e3 100644 --- a/apps/tailwind-components/components/form/Fields.vue +++ b/apps/tailwind-components/components/form/Fields.vue @@ -9,6 +9,7 @@ import type { } from "../../../metadata-utils/src/types"; const props = defineProps<{ + schemaId: string; metadata: ITableMetaData; data: Record[]; }>(); @@ -110,6 +111,7 @@ defineExpose({ validate }); - + diff --git a/apps/tailwind-components/components/input/RadioIcon.vue b/apps/tailwind-components/components/input/RadioIcon.vue index 10497b745d..13fe411a86 100644 --- a/apps/tailwind-components/components/input/RadioIcon.vue +++ b/apps/tailwind-components/components/input/RadioIcon.vue @@ -12,9 +12,11 @@ r="9" stroke-width="1" fill="none" - class="stroke-gray-600" + class="stroke-current" :class="{ - 'fill-yellow-500 stroke-none': checked, + 'fill-input hover:fill-input-checked hover:stroke-none focus:fill-input-checked focus:stroke-none': + !checked, + 'fill-input-checked stroke-none': checked, }" /> diff --git a/apps/tailwind-components/components/input/Ref.vue b/apps/tailwind-components/components/input/Ref.vue new file mode 100644 index 0000000000..9e12908ab2 --- /dev/null +++ b/apps/tailwind-components/components/input/Ref.vue @@ -0,0 +1,272 @@ + + + + + + {{ label }} + + + + + Search + + + Clear all + + + + + search in {{ columnName }} + + + + + + + + load {{ entitiesLeftToLoad }} more + + + No results found + diff --git a/apps/tailwind-components/components/input/Search.vue b/apps/tailwind-components/components/input/Search.vue new file mode 100644 index 0000000000..baa9382199 --- /dev/null +++ b/apps/tailwind-components/components/input/Search.vue @@ -0,0 +1,47 @@ + + + + + + + handleInput((event.target as HTMLInputElement).value)" + class="w-full pr-4 font-sans text-black bg-white outline-none rounded-search-input h-10 ring-red-500 pl-10 shadow-search-input focus:shadow-search-input hover:shadow-search-input" + :class="[ + inverted + ? 'border-search-input-mobile border' + : 'border-search-input focus:border-white', + ]" + :placeholder="placeholder" + /> + + diff --git a/apps/tailwind-components/pages/Form.story.vue b/apps/tailwind-components/pages/Form.story.vue index ae2da76c46..c071e679ca 100644 --- a/apps/tailwind-components/pages/Form.story.vue +++ b/apps/tailwind-components/pages/Form.story.vue @@ -8,26 +8,38 @@ import type { ITableMetaData, } from "../../metadata-utils/src/types"; -const sampleType = ref("simple"); +const exampleName = ref("simple"); + +const exampleMap = ref({ + simple: { + schemaId: "pet store", + tableId: "Pet", + }, + "pet store user": { + schemaId: "pet store", + tableId: "User", + }, + complex: { + schemaId: "catalogue-demo", + tableId: "Resources", + }, +}); // just assuming that the table is there for the demo -const schemaId = computed(() => - sampleType.value === "simple" ? "pet store" : "catalogue-demo" -); -const tableId = computed(() => - sampleType.value === "simple" ? "Pet" : "Resources" -); +const exampleConfig = computed(() => exampleMap.value[exampleName.value]); const { data: schemaMeta, refresh: refetchMetadata, status, -} = await useAsyncData("form sample", () => fetchMetadata(schemaId.value)); +} = await useAsyncData("form sample", () => + fetchMetadata(exampleConfig.value.schemaId) +); const tableMeta = computed( () => (schemaMeta.value as ISchemaMetaData)?.tables.find( - (table) => table.id === tableId.value + (table) => table.id === exampleConfig.value.tableId ) as ITableMetaData ); @@ -134,9 +146,11 @@ watch( - + Demo controls, settings and status @@ -152,15 +166,16 @@ watch( Simple form example Complex form example + Pet store user - schema id = {{ schemaId }} - table id = {{ tableId }} + schema id = {{ exampleConfig.schemaId }} + table id = {{ exampleConfig.tableId }} (["tomatoes", "basil"]); @@ -40,7 +40,7 @@ +const schemaId = ref("pet store"); +const tableId = ref("Pet"); +const labelTemplate = ref("${name}"); +const value = ref([{ name: "spike" }]); +const value2 = ref([{ name: "spike" }]); + + + + + Ref array example + Select pets by name + + value selected: {{ value }} + + + Ref example + Select pets by name + + + value selected: {{ value2 }} + diff --git a/apps/tailwind-components/pages/input/Search.story.vue b/apps/tailwind-components/pages/input/Search.story.vue new file mode 100644 index 0000000000..4db44f526c --- /dev/null +++ b/apps/tailwind-components/pages/input/Search.story.vue @@ -0,0 +1,13 @@ + + + + + + + value: {{ searchValue }} + + + diff --git a/apps/tailwind-components/plugins/clearSession.client.ts b/apps/tailwind-components/plugins/clearSession.client.ts new file mode 100644 index 0000000000..3ee1b74dac --- /dev/null +++ b/apps/tailwind-components/plugins/clearSession.client.ts @@ -0,0 +1,6 @@ +export default defineNuxtPlugin(() => { + if (process.client) { + sessionStorage.clear(); + console.log('Session storage cleared on app start. Removed caches of getSchema'); + } +}); \ No newline at end of file diff --git a/apps/tailwind-components/tailwind.config.js b/apps/tailwind-components/tailwind.config.js index 838007af18..8222725971 100644 --- a/apps/tailwind-components/tailwind.config.js +++ b/apps/tailwind-components/tailwind.config.js @@ -171,6 +171,7 @@ module.exports = { "button-outline-hover": "var(--text-color-button-outline-hover)", "button-disabled": "var(--text-color-button-disabled)", "button-disabled-hover": "var(--text-color-button-disabled-hover)", + "button-text": "var(--text-color-button-text)", "menu": "var(--text-color-menu)", "sub-menu": "var(--text-color-sub-menu)", "sub-menu-hover": "var(--text-color-sub-menu-hover)", diff --git a/apps/tailwind-components/types/types.ts b/apps/tailwind-components/types/types.ts index 324180c596..00917894f7 100644 --- a/apps/tailwind-components/types/types.ts +++ b/apps/tailwind-components/types/types.ts @@ -15,6 +15,11 @@ export interface INode { description?: string; } +export interface IValueLabel { + value: any; + label?: string; +} + export interface ITreeNode extends INode { children: ITreeNode[]; diff --git a/e2e/tests/tailwind-components/form/renderForm.spec.ts b/e2e/tests/tailwind-components/form/renderForm.spec.ts new file mode 100644 index 0000000000..06602fce43 --- /dev/null +++ b/e2e/tests/tailwind-components/form/renderForm.spec.ts @@ -0,0 +1,27 @@ +import { test, expect } from "@playwright/test"; +import playwrightConfig from '../../../playwright.config'; + +const route = playwrightConfig?.use?.baseURL?.startsWith('http://localhost') ? '' : '/apps/tailwind-components/#/'; + +test("it should render the form", async ({ page }) => { + await page.goto(`${route}Form.story`); + await expect(page.getByText('nameRequiredthe name')).toBeVisible(); + await expect(page.locator('div').filter({ hasText: /^statusRequired$/ }).nth(1)).toBeVisible(); +}); + +test("it should update the model value when a field is filled out", async ({ page }) => { + await page.goto(`${route}Form.story`); + await page.getByLabel('name').click(); + await page.getByLabel('name').fill('test'); + //this test will be flakey, replace by specific tests? + //await expect(page.getByRole('main')).toContainText('dataMap: { "name": "test", "category": "", "photoUrls": "", "details": "", "status": "", "tags": "", "weight": "", "orders": "", "mg_draft": "", "mg_insertedBy": "", "mg_insertedOn": "", "mg_updatedBy": "", "mg_updatedOn": "" } errorMap: { "name": [], "category": [], "photoUrls": [], "details": [], "status": [], "tags": [], "weight": [], "orders": [], "mg_draft": [], "mg_insertedBy": [], "mg_insertedOn": [], "mg_updatedBy": [], "mg_updatedOn": [] }'); + await page.locator('label').filter({ hasText: 'dog' }).getByRole('img').click(); + await expect(page.getByRole('button', { name: 'dog' })).toBeVisible(); +}); + +test("should also work for refs", async ({ page }) => { + await page.goto(`${route}Form.story`); + await page.getByRole('combobox').selectOption('pet store user'); + await page.locator('label').filter({ hasText: 'pooky' }).locator('rect').click(); + await expect(page.getByRole('button', { name: 'pooky' })).toBeVisible(); +}); \ No newline at end of file