Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
da9161e
feat: rough new showcase form
tpluscode Sep 5, 2025
e8e07f0
feat: added link to header menu
tpluscode Sep 5, 2025
e8c4dff
Merge remote-tracking branch 'origin/main' into showcase-form
tpluscode Sep 8, 2025
3d64541
feat(showcase): submission API
tpluscode Sep 8, 2025
abdee3a
feat(showcases): notification on submission
tpluscode Sep 9, 2025
b005825
style: eslint fixes
tpluscode Sep 9, 2025
4f7a4fe
chore: throw when submitting in production
tpluscode Sep 9, 2025
020ae91
feat(showcase): submission API
tpluscode Sep 8, 2025
ddbf06d
feat(showcases): notification on submission
tpluscode Sep 9, 2025
b9a2082
refactor: preparation for GH PRs for showcases
tpluscode Sep 9, 2025
5b0d656
Merge branch 'showcase-form' of github.com:opendata-swiss/metadata.sw…
tpluscode Sep 9, 2025
10c6096
Merge pull request #9 from opendata-swiss/showcase-image-submit
tpluscode Sep 11, 2025
bfe66c5
Merge branch 'main' into showcase-form
tpluscode Sep 15, 2025
2c7ad78
Merge remote-tracking branch 'origin/main' into showcase-form
tpluscode Sep 19, 2025
d9f1c49
fix: main menu link
tpluscode Sep 19, 2025
28fa372
feat(submit form): minimal client-side validation
tpluscode Sep 19, 2025
bc8da27
feat(submit form): server-side validation
tpluscode Sep 22, 2025
fc71d91
feat: display server validation issues on page
tpluscode Sep 23, 2025
77d433a
refactor: save uploaded file only when validation is successful
tpluscode Sep 23, 2025
8debb52
chore: log unhandled field
tpluscode Sep 23, 2025
adf966b
Merge remote-tracking branch 'origin/main' into showcase-form
tpluscode Sep 23, 2025
047c18a
feat(showcases): add margin-top to form groups
tpluscode Sep 24, 2025
5d0a469
revert(showcases): remove showcases submenu from main menu
tpluscode Sep 24, 2025
d4931b8
Merge branch 'main' into showcase-form
tpluscode Sep 24, 2025
db44705
fix(showcases): placeholder for tags input
tpluscode Sep 25, 2025
3957ea7
Merge remote-tracking branch 'origin/main' into showcase-form
tpluscode Sep 25, 2025
e3c4e82
Merge remote-tracking branch 'origin/main' into showcase-form
tpluscode Sep 26, 2025
77a1e47
fix: bad config merge
tpluscode Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions opendata.swiss/ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ logs
.DS_Store
.fleet
.idea

# ignore images from uploads folder
public/img/uploads/
4 changes: 4 additions & 0 deletions opendata.swiss/ui/app/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@
.v-select {
background-color: white;
}

.form__group__input:has(> *:user-invalid), .form__group__select:has(> select:invalid) {
color: #b20000;
}
13 changes: 7 additions & 6 deletions opendata.swiss/ui/app/components/OdsButton.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<template>
<button
type="button"
:class="['btn', 'btn--base', classes]"
:aria-label="title"
:title="title"
:type="!submit ? 'button' : undefined"
:class="['btn', 'btn--base', classes]"
:aria-label="title"
:title="title"
>
<slot name="icon">
<SvgIcon v-if="icon" :icon="icon" :size="size" class="btn__icon" />
<SvgIcon v-if="icon" :icon="icon" :size="size" class="btn__icon"/>
</slot>
<span class="btn__text">
<a v-if="href" :href="href" >
<a v-if="href" :href="href">
<slot>{{ title }}</slot>
</a>
<slot v-else>{{ title }}</slot>
Expand All @@ -30,6 +30,7 @@ const { title, iconOnly = false, ...props } = defineProps<{
iconRight?: boolean
icon?: string
href?: string
submit?: boolean
}>()

const classes = computed(() => {
Expand Down
155 changes: 155 additions & 0 deletions opendata.swiss/ui/app/components/OdsInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<template>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is direct copy from swiss design

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input css is currently broken. There is a conflict with vuetify (which is not removed here but in main)

<div class="form__group__input">
<label v-if="label" :for="id" :class="labelClasses">
{{ label }}<span v-if="required" class="form__group__required" />
</label>
<input
:id="id"
:type="type"
:class="classes"
:name="id"
:placeholder="placeholder"
:value="value"
:min="min"
:max="max"
:step="step"
:pattern="pattern"
:autocomplete="autocomplete"
:readonly="readonly"
:required="required"
:accept="accept"
@input.stop="onInput"
/>
<div
v-if="message"
class="badge badge--sm"
:class="`badge--${messageType}`"
>
{{ message }}
</div>
</div>
</template>

<script setup lang="ts">
import { computed, type PropType } from 'vue'

const props = defineProps({
type: {
type: String,
validator: (prop) =>
[
'color',
'date',
'datetime-local',
'email',
'file',
'month',
'number',
'password',
'range',
'search',
'tel',
'text',
'time',
'url',
'week',
'submit',
].includes(prop as string),
default: () => 'text',
},
variant: {
type: String,
validator: (prop) => ['outline', 'negative'].includes(prop as string),
default: () => 'outline',
},
message: {
type: String,
default: () => undefined,
},
onInput: {
type: Function as PropType<(event: Event) => void>,
default: () => ({}),
},
messageType: {
type: String,
validator: (prop) =>
['error', 'warning', 'success', 'info'].includes(prop as string),
default: () => 'error',
},
size: {
type: String,
validator: (prop) => ['sm', 'base', 'lg'].includes(prop as string),
default: () => undefined,
},
label: {
type: String,
default: () => undefined,
},
hideLabel: {
type: Boolean,
default: () => false,
},
placeholder: {
type: String,
default: () => undefined,
},
value: {
type: String,
default: () => undefined,
},
id: {
type: String,
default: () => undefined,
},
min: {
type: Number,
default: () => undefined,
},
max: {
type: Number,
default: () => undefined,
},
step: {
type: Number,
default: () => undefined,
},
pattern: {
type: String,
default: () => undefined,
},
autocomplete: {
type: String,
default: () => undefined,
},
readonly: {
type: Boolean,
default: () => false,
},
required: {
type: Boolean,
default: () => false,
},
accept: {
type: String,
default: () => undefined,
}
})

const classes = computed(() => {
let base = ''
if (props.variant) base += `input--${props.variant} `
if (props.size) base += `input--${props.size} `
if (props.message) base += `input--${props.messageType} `
if (props.type === 'submit') base += 'input--submit'
return base
})

const labelClasses = computed(() => {
let base = ''
if (props.variant === 'negative') base += `text--negative `
if (props.size) base += `text--${props.size} `
if (props.hideLabel) base += `sr-only `
if (props.required) base += `text--asterisk `
return base
})
</script>
49 changes: 49 additions & 0 deletions opendata.swiss/ui/app/components/OdsNotificationBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a copy from swiss design example plus...

<div
:open="open || undefined"
:class="['notification-banner', 'notification', `notification--${type}`]"
:style="{
transitionDuration: `${closeDuration}`,
}"
>
<div
class="notification-banner__wrapper"
:style="{
transitionDelay: `${closeDuration}`,
}"
>
<p class="notification-banner__infos">
<slot />
</p>
<slot name="buttons" />
</div>
</div>
</template>

<script setup lang="ts">
const { open = true, closeDuration = '0.3s' } = defineProps<{
type: 'info' | 'success' | 'warning' | 'error'
open?: boolean
closeDuration?: string
}>()
</script>

<style scoped>
.notification-banner {
display: block;
transition-property: padding, opacity;
transition-timing-function: ease-in-out, ease-in-out;
}
.notification-banner:not([open]) {
padding: 0;
opacity: 0;
overflow: hidden;
}
.notification-banner__wrapper {
transition-property: height;
transition-duration: 0s;
}
.notification-banner:not([open]) .notification-banner__wrapper {
height: 0;
}
Comment on lines +32 to +48
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...a transition which smoothly collapses the notification when open is set from true to false

</style>
109 changes: 109 additions & 0 deletions opendata.swiss/ui/app/components/OdsTextarea.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<template>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also a copy from swiss design

<div class="form__group__input">
<label v-if="label" :for="id" :class="labelClasses">
{{ label }}
</label>
<textarea
:id="id"
:class="classes"
:name="id"
:rows="rows"
:cols="cols"
:maxlength="maxlength"
:minlength="minlength"
:placeholder="placeholder"
:required="required"
/>
<div
v-if="message"
class="badge badge--sm"
:class="`badge--${messageType}`"
>
{{ message }}
</div>
</div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps({
variant: {
type: String,
validator: (prop) => ['outline', 'negative'].includes(prop as string),
default: () => undefined,
},
size: {
type: String,
validator: (prop) => ['sm', 'base', 'lg'].includes(prop as string),
default: () => undefined,
},
id: {
type: String,
default: () => undefined,
},
name: {
type: String,
default: () => undefined,
},
label: {
type: String,
default: () => undefined,
},
placeholder: {
type: String,
default: () => undefined,
},
rows: {
type: Number,
default: () => 4,
},
cols: {
type: Number,
default: () => 50,
},
message: {
type: String,
default: () => undefined,
},
messageType: {
type: String,
validator: (prop) =>
['error', 'warning', 'success', 'info'].includes(prop as string),
default: () => undefined,
},
required: {
type: Boolean,
default: () => false,
},
resizable: {
type: Boolean,
default: () => true,
},
maxlength: {
type: Number,
default: () => undefined,
},
minlength: {
type: Number,
default: () => undefined,
},
})
const classes = computed(() => {
let base = ''
if (props.variant) base += `input--${props.variant} `
if (props.size) base += `input--${props.size} `
if (props.messageType) base += `input--${props.messageType} `
if (!props.resizable) base += 'textarea--public'
return base
})
const labelClasses = computed(() => {
let base = ''
if (props.variant === 'negative') base += `text--negative `
if (props.size) base += `text--${props.size} `
if (props.required) base += `text--asterisk `
return base
})
</script>
14 changes: 13 additions & 1 deletion opendata.swiss/ui/app/components/dataset/OdsMultiSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@
<template #option="option">
<span>
{{ option.title }}
<span style="float: right; color: #888;">({{ option.count }})</span>
<span v-if="option.count" style="float: right; color: #888;">({{ option.count }})</span>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hiding the ({count}) text when it's not set to avoid empty ()

</span>
</template>
<template
#selected-option="option"
>
{{ option.title }}
<input type="hidden" :name="name" :value="option.id">
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hidden input makes it possible to use the component in native <form>

</template>
</VSelect>
<div class="select__icon">
<svg role="presentation" aria-hidden="true" viewBox="0 0 24 24">
Expand Down Expand Up @@ -226,3 +232,9 @@ watch(
}
)
</script>

<style scoped>
input.vs__selected {
cursor: default;
}
</style>
2 changes: 1 addition & 1 deletion opendata.swiss/ui/app/constants/navigation-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const APP_NAVIGATION_ITEMS: OdsNavTabItem[] = [
},
{
label: 'message.header.navigation.showcases',
to: '/showcases'
to: '/showcases',
},
{
label: 'message.header.navigation.blog',
Expand Down
Loading