Skip to content

Commit

Permalink
I9857 slider (#343)
Browse files Browse the repository at this point in the history
* pkp/pkp-lib#9857 FieldSlider initial implementation

* pkp/pkp-lib#9857 Update color classes

* pkp/pkp-lib#9857 Add step prop
  • Loading branch information
jardakotesovec committed Apr 10, 2024
1 parent b2ac693 commit 8078854
Show file tree
Hide file tree
Showing 13 changed files with 289 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .storybook/preview.js
Expand Up @@ -2,6 +2,7 @@

import {withThemeByDataAttribute} from '@storybook/addon-themes';
import {mockDateDecorator} from 'storybook-mock-date-decorator';
import PrimeVue from 'primevue/config';

import {setup} from '@storybook/vue3';
import GlobalMixins from '@/mixins/global.js';
Expand Down Expand Up @@ -51,6 +52,10 @@ initialize({

setup((app) => {
app.use(pinia);
app.use(PrimeVue, {
unstyled: true,
});

app.mixin(GlobalMixins);

app.use(FloatingVue, {
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -30,6 +30,7 @@
"moment": "^2.29.4",
"ofetch": "^1.3.3",
"pinia": "^2.1.7",
"primevue": "^3.50.0",
"tiny-emitter": "^2.1.0",
"tinymce": "^5.10.7",
"uuid": "^9.0.0",
Expand Down
1 change: 1 addition & 0 deletions src/components/Form/FormFieldLabel.vue
Expand Up @@ -18,6 +18,7 @@
export default {
name: 'FormFieldLabel',
props: {
labelId: String,
controlId: String,
label: String,
localeLabel: String,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Form/FormGroup.vue
Expand Up @@ -75,6 +75,7 @@ import FieldShowEnsuringLink from './fields/FieldShowEnsuringLink.vue';
import FieldText from './fields/FieldText.vue';
import FieldTextarea from './fields/FieldTextarea.vue';
import FieldUpload from './fields/FieldUpload.vue';
import FieldSlider from './fields/FieldSlider.vue';
import FieldUploadImage from './fields/FieldUploadImage.vue';
export default {
Expand All @@ -101,6 +102,7 @@ export default {
FieldShowEnsuringLink,
FieldText,
FieldTextarea,
FieldSlider,
FieldUpload,
FieldUploadImage,
},
Expand Down
11 changes: 10 additions & 1 deletion src/components/Form/fields/FieldBase.vue
Expand Up @@ -107,6 +107,15 @@ export default {
return this.isMultilingual ? this.name + '-' + this.localeKey : this.name;
},
/**
* In case field is not using input element, its necessary to reference the label via aria-labelby (for example FieldSlider)
*
* @return {String}
*/
labelId() {
return this.compileId('control');
},
/**
* A unique id for the label and control
*
Expand Down Expand Up @@ -185,7 +194,7 @@ export default {
if (this.isMultilingual) {
ids.push(this.multilingualProgressId);
}
return ids.length ? ids.join(' ') : false;
return ids.length ? ids.join(' ') : undefined;
},
/**
Expand Down
7 changes: 7 additions & 0 deletions src/components/Form/fields/FieldSlider.mdx
@@ -0,0 +1,7 @@
import {Primary, Controls, Stories, Meta, ArgTypes} from '@storybook/blocks';

import * as FieldSliderStories from './FieldSlider.stories.js';

<Meta of={FieldSliderStories} />

# FieldSlider
34 changes: 34 additions & 0 deletions src/components/Form/fields/FieldSlider.stories.js
@@ -0,0 +1,34 @@
import FieldSlider from './FieldSlider.vue';

import FieldBaseMock from '../mocks/field-base';
import FieldSliderMock from '../mocks/field-slider';

export default {
title: 'Forms/FieldSlider',
component: FieldSlider,
render: (args) => ({
components: {FieldSlider},
setup() {
function change(name, prop, newValue, localeKey) {
if (localeKey) {
args[prop][localeKey] = newValue;
} else {
args[prop] = newValue;
}
}

return {args, change};
},
template: `
<FieldSlider v-bind="args" @change="change" />
`,
}),
};

export const Base = {
args: {
...FieldBaseMock,
...FieldSliderMock,
// description: 'slider description',
},
};
184 changes: 184 additions & 0 deletions src/components/Form/fields/FieldSlider.vue
@@ -0,0 +1,184 @@
<template>
<div class="mt-5 max-w-lg">
<div class="pkpFormField__heading">
<form-field-label
:id="labelId"
:label="label"
:locale-label="localeLabel"
:is-required="isRequired"
:required-label="t('common.required')"
:multilingual-label="multilingualLabel"
/>
<tooltip
v-if="isPrimaryLocale && tooltip"
aria-hidden="true"
:tooltip="tooltip"
label=""
/>
<span
v-if="isPrimaryLocale && tooltip"
:id="describedByTooltipId"
class="-screenReader"
v-html="tooltip"
/>
<help-button
v-if="isPrimaryLocale && helpTopic"
:id="describedByHelpId"
:topic="helpTopic"
:section="helpSection"
:label="t('help.help')"
/>
</div>
<div
v-if="isPrimaryLocale && description"
:id="describedByDescriptionId"
class="pkpFormField__description"
v-html="description"
/>
<div class="mt-2 flex">
<div class="mt-3 grow">
<div class="px-2">
<Slider
v-model="currentValue"
:min="min"
:max="max"
:step="step"
class="w-full"
:aria-labelledby="labelId"
:pt="sliderStyling"
/>
</div>
<div class="mt-2 flex justify-between text-base-normal text-secondary">
<div>{{ minLabel || min }}</div>
<div>{{ maxLabel || max }}</div>
</div>
</div>
<div
class="ms-3 w-48 self-start rounded border border-form-fields p-2 text-center text-base-normal text-secondary"
>
{{ displayedValue }}
</div>
</div>
</div>
</template>

<script>
import FieldBase from './FieldBase.vue';
import Slider from 'primevue/slider';
export default {
name: 'FieldSlider',
components: {Slider},
extends: FieldBase,
props: {
min: {required: true, type: Number},
max: {required: true, type: Number},
step: {required: false, type: Number, default: 1},
minLabel: {required: false, type: String, default: null},
maxLabel: {required: false, type: String, default: null},
/** Expecting translation key, which gets the value passed to it as {$value} */
valueLabel: {required: false, type: String, default: null},
valueLabelMin: {required: false, type: String, default: null},
valueLabelMax: {required: false, type: String, default: null},
},
data() {
const sliderStyling = {
root: ({props}) => ({
class: [
'relative',
// Size
{
'h-1 w-60': props.orientation == 'horizontal',
},
// Shape
'border-0',
// Colors
'bg-[#BBBBBB]',
// States
{
// Disabled use cases is not used, styling to be refine when needed
'select-none pointer-events-none cursor-default': props.disabled,
},
],
}),
range: ({props}) => ({
class: [
// Position
'block absolute',
{
'top-0 left-0': props.orientation == 'horizontal',
},
//Size
{
'h-full': props.orientation == 'horizontal',
},
// Colors
'bg-primary',
],
}),
handle: ({props, options}) => {
return {
id: this.controlId,
'aria-valuetext': this.displayedValue,
'aria-describedby': this.describedByIds,
class: [
'block',
// Size
'h-[1.143rem]',
'w-[1.143rem]',
{
'top-[50%] mt-[-0.5715rem] ml-[-0.5715rem]':
props.orientation == 'horizontal',
'left-[50%] mb-[-0.5715rem] ml-[-0.5715rem]':
props.orientation == 'vertical',
},
// Shape
'rounded-full',
'border-2',
// Colors
'bg-primary',
'border-primary',
// States
'hover:bg-hover hover:border-primary-500',
'focus-visible:outline-none focus-visible:outline-offset-0 focus-visible:ring',
'ring-primary/50',
// Transitions
'transition duration-200',
// Misc
'cursor-grab',
'touch-action-none',
],
};
},
// Additional styling is available if we need to have two handlers for range capabilities
};
return {sliderStyling};
},
computed: {
displayedValue() {
if (this.valueLabelMin && this.currentValue == this.min) {
return this.replaceLocaleParams(this.valueLabelMin, {
value: this.currentValue,
});
}
if (this.valueLabelMax && this.currentValue == this.max) {
return this.replaceLocaleParams(this.valueLabelMax, {
value: this.currentValue,
});
}
if (this.valueLabel) {
return this.replaceLocaleParams(this.valueLabel, {
value: this.currentValue,
});
}
return this.currentValue;
},
},
};
</script>
11 changes: 11 additions & 0 deletions src/components/Form/mocks/field-slider.js
@@ -0,0 +1,11 @@
export default {
name: 'slider',
component: 'field-slider',
label: 'Review Request Response - Before Due Date',
value: 20,
min: 0,
minLabel: 'None',
max: 35,
valueLabel: '{$value} days after due date',
valueLabelMin: 'No reminder set',
};
8 changes: 8 additions & 0 deletions src/composables/useFiltersForm.js
Expand Up @@ -56,6 +56,14 @@ export function useFiltersForm(_filtersForm) {
label: option.label,
value: option.value,
});
} else if (field.component === 'field-slider') {
if (fieldValue !== field.min) {
list.push({
fieldLabel: field.label,
value: fieldValue,
label: fieldValue,
});
}
} else {
list.push({
fieldLabel: field.label,
Expand Down
19 changes: 16 additions & 3 deletions src/composables/useForm.js
Expand Up @@ -6,6 +6,20 @@ function getField(form, name) {
return fields.find((field) => field.name === name);
}

function getClearValue(field, localeKey = null) {
if (localeKey) {
if (field.component === 'field-slider') {
return field.min;
}
return Array.isArray(field.value[localeKey]) || field.selected ? [] : '';
}

if (field.component === 'field-slider') {
return field.min;
}
return Array.isArray(field.value) || field.selected ? [] : '';
}

function mapFromSelectedToValue(selected) {
return selected.map((iv) => iv.value);
}
Expand Down Expand Up @@ -43,13 +57,12 @@ export function useForm(_form) {
const newValueMultilingual = {};
form.value.supportedFormLocales.forEach((localeObject) => {
const localeKey = localeObject.key;
const newValue =
Array.isArray(field.value[localeKey]) || field.selected ? [] : '';
const newValue = getClearValue(field, localeKey);
newValueMultilingual[localeKey] = newValue;
});
setValue(field.name, newValueMultilingual);
} else {
const newValue = Array.isArray(field.value) || field.selected ? [] : '';
const newValue = getClearValue(field);
setValue(field.name, newValue);
}
});
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.js
Expand Up @@ -56,6 +56,7 @@ export default {
},
borderRadius: {
DEFAULT: '4px',
full: '9999px',
},
boxShadow: {
DEFAULT: '0 0 4px rgba(0, 0, 0, 0.5);',
Expand Down

0 comments on commit 8078854

Please sign in to comment.