1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-15 01:28:30 +00:00

Merge branch 'checkbox-refactor' into 'develop'

Refactor checkbox component

See merge request 
This commit is contained in:
Jonathan Adeline 2024-01-30 08:33:45 +00:00
commit 6d0a09f27c
18 changed files with 489 additions and 107 deletions
changelog/entries/unreleased/refactor
web-frontend

View file

@ -0,0 +1,7 @@
{
"type": "refactor",
"message": "Redesign checkbox component",
"issue_number": 1918,
"bullet_points": [],
"created_at": "2024-01-04"
}

View file

@ -5,6 +5,7 @@
@import 'alert';
@import 'form';
@import 'switch_input';
@import 'checkbox';
@import 'box';
@import 'layout';
@import 'tree';

View file

@ -136,7 +136,6 @@
.api-docs__heading-wrapper {
display: flex;
justify-content: space-between;
align-items: baseline;
@media screen and (max-width: $api-docs-breakpoint) {
display: block;

View file

@ -76,7 +76,6 @@
margin-bottom: 20px;
padding: 16px 16px 0 16px;
display: flex;
align-items: baseline;
}
.api-docs__example-type-item {

View file

@ -0,0 +1,75 @@
.checkbox {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
max-width: 100%;
}
.checkbox__button {
width: 16px;
height: 16px;
position: relative;
text-align: center;
cursor: pointer;
user-select: none;
border: 1px solid $palette-neutral-400;
box-shadow: 0 1px 2px 0 rgba(7, 8, 16, 0.1);
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
background: $white;
@include rounded($rounded);
:focus & {
@include add-elevation($elevation-low);
}
.checkbox--error:not(.checkbox--disabled) & {
border: 1px solid $palette-red-600;
}
.checkbox--checked.checkbox--disabled & {
cursor: not-allowed;
background-color: $palette-neutral-500;
box-shadow: none;
border: 1px solid $palette-neutral-500;
}
.checkbox--disabled:not(.checkbox--checked) & {
cursor: not-allowed;
background-color: $palette-neutral-100;
box-shadow: none;
}
.checkbox--checked:not(.checkbox--disabled) & {
background: $palette-blue-500;
box-shadow: none;
border: 1px solid $palette-blue-500;
}
.checkbox:active:not(.checkbox--checked) & {
background-color: $palette-neutral-50;
box-shadow: none;
}
:active.checkbox--checked:not(.checkbox--disabled) & {
background-color: $palette-blue-700;
border: 1px solid $palette-blue-700;
}
}
.checkbox__label {
flex: 1;
cursor: pointer;
line-height: 15px;
@extend %ellipsis;
.checkbox--disabled & {
color: $palette-neutral-700;
}
}

View file

@ -190,53 +190,6 @@
}
}
.checkbox {
position: relative;
height: 28px;
cursor: pointer;
user-select: none;
@include flex-align-items(4px);
&::before {
content: '';
border: 1px solid $color-neutral-400;
width: 20px;
height: 20px;
text-align: center;
background-color: $white;
color: $color-success-600;
@include rounded($rounded);
}
&.checkbox--disabled {
cursor: not-allowed;
color: $color-neutral-500;
&::before {
background-color: $color-neutral-50;
color: $color-neutral-500;
}
}
}
.checkbox__label {
color: $palette-neutral-900;
cursor: pointer;
.checkbox.active & {
color: $palette-neutral-1200;
}
}
.checkbox__checked-icon {
position: absolute;
top: 50%;
left: 2px;
transform: translateY(-50%);
color: $color-success-600;
font-size: 16px;
}
.radio {
display: flex;
align-items: center;

View file

@ -1,43 +1,117 @@
<template>
<div class="checkbox" :class="classNames" @click="toggle(value)">
<i v-if="value === true" class="checkbox__checked-icon iconoir-check"></i>
<label class="checkbox__label"><slot></slot></label>
<div class="checkbox" :class="classNames" @click="toggle(checked)">
<div class="checkbox__button">
<svg
v-show="checked && !indeterminate"
class="checkbox__tick"
xmlns="http://www.w3.org/2000/svg"
width="9"
height="8"
viewBox="0 0 9 8"
fill="none"
>
<g clip-path="url(#clip0_1138_66)">
<path
d="M1.5179 4.4821L3.18211 6.18211L7.42475 2.15368"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_1138_66">
<rect
width="8"
height="8"
fill="white"
transform="translate(0.5)"
/>
</clipPath>
</defs>
</svg>
<svg
v-show="checked && indeterminate"
class="checkbox__tick-indeterminate"
xmlns="http://www.w3.org/2000/svg"
width="8"
height="8"
viewBox="0 0 8 8"
fill="none"
>
<path
d="M1.5 4L6.5 4"
stroke="white"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</div>
<label v-if="hasSlot" class="checkbox__label">
<slot></slot>
</label>
</div>
</template>
<script>
export default {
name: 'Checkbox',
model: {
prop: 'checked',
event: 'input',
},
props: {
value: {
/**
* The state of the checkbox.
*/
checked: {
type: Boolean,
required: false,
default: false,
},
/**
* Whether the checkbox is disabled.
*/
disabled: {
type: Boolean,
required: false,
default: false,
},
/**
* Whether the checkbox is in error state.
*/
error: {
type: Boolean,
required: false,
default: false,
},
/**
* Whether the checkbox is in indeterminate state
*/
indeterminate: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
classNames() {
return {
'checkbox--has-content': Object.prototype.hasOwnProperty.call(
this.$slots,
'default'
),
'checkbox--disabled': this.disabled,
active: this.value === true,
'checkbox--checked': this.checked,
'checkbox--error': this.error,
}
},
hasSlot() {
return !!this.$slots.default
},
},
methods: {
toggle(value) {
if (this.disabled) {
return
}
this.$emit('input', !value)
toggle(checked) {
if (this.disabled) return
this.$emit('input', !checked)
},
},
}

View file

@ -2,9 +2,9 @@
<ul class="list">
<li v-for="(item, index) in items" :key="index" class="list__item">
<div class="list__left-side">
<checkbox
<Checkbox
v-if="selectable"
:value="selectedItemsIds.includes(item.id)"
:checked="selectedItemsIds.includes(item.id)"
@input="selected($event, item, index)"
/>
<slot name="left-side" :item="item"></slot>

View file

@ -173,7 +173,7 @@
value: {{ checkbox }}
<br />
<br />
<Checkbox v-model="checkbox"></Checkbox>
<Checkbox class="margin-bottom-1" v-model="checkbox"></Checkbox>
<Checkbox v-model="checkbox">With text</Checkbox>
</div>
</div>

View file

@ -70,7 +70,7 @@
</Dropdown>
<Checkbox
v-if="includeUserFieldsCheckbox"
:value="value.userFieldNames"
:checked="value.userFieldNames"
class="api-docs__example-type-item"
@input="$emit('input', { userFieldNames: $event, type: value.type })"
>{{ $t('apiDocsExample.userFieldNames') }}</Checkbox

View file

@ -1,6 +1,6 @@
<template>
<div>
<div class="control">
<div class="control margin-bottom-2">
<label class="control__label control__label--small">{{
$t('fieldDateSubForm.dateFormatLabel')
}}</label>
@ -29,34 +29,39 @@
</div>
<div class="control">
<div class="control__elements">
<Checkbox v-model="values.date_include_time">{{
$t('fieldDateSubForm.includeTimeLabel')
}}</Checkbox>
<div v-show="values.date_include_time" class="control margin-top-2">
<label class="control__label control__label--small">{{
$t('fieldDateSubForm.timeFormatLabel')
}}</label>
<div class="control__elements">
<Dropdown
v-model="values.date_time_format"
:fixed-items="true"
small
@hide="$v.values.date_time_format.$touch()"
>
<DropdownItem
:name="$t('fieldDateSubForm.24Hour') + ' (23:00)'"
value="24"
></DropdownItem>
<DropdownItem
:name="$t('fieldDateSubForm.12Hour') + ' (11:00 PM)'"
value="12"
></DropdownItem>
</Dropdown>
<div class="margin-bottom-1">
<Checkbox v-model="values.date_include_time">{{
$t('fieldDateSubForm.includeTimeLabel')
}}</Checkbox>
<div
v-show="values.date_include_time"
class="control margin-bottom-2 margin-top-2"
>
<label class="control__label control__label--small">{{
$t('fieldDateSubForm.timeFormatLabel')
}}</label>
<div class="control__elements">
<Dropdown
v-model="values.date_time_format"
:fixed-items="true"
small
@hide="$v.values.date_time_format.$touch()"
>
<DropdownItem
:name="$t('fieldDateSubForm.24Hour') + ' (23:00)'"
value="24"
></DropdownItem>
<DropdownItem
:name="$t('fieldDateSubForm.12Hour') + ' (11:00 PM)'"
value="12"
></DropdownItem>
</Dropdown>
</div>
</div>
</div>
<Checkbox
v-show="values.date_include_time"
:value="values.date_force_timezone !== null"
:checked="values.date_force_timezone !== null"
@input="toggleForceTimezone()"
>{{ $t('fieldDateSubForm.forceTimezoneLabel') }}</Checkbox
>
@ -69,7 +74,7 @@
<label class="control__label control__label--small">{{
$t('fieldDateSubForm.forceTimezoneValue')
}}</label>
<div class="control__elements">
<div class="control__elements margin-top-2 margin-bottom-2">
<PaginatedDropdown
:value="values.date_force_timezone"
:fetch-page="fetchTimezonePage"
@ -89,7 +94,7 @@
values.date_include_time &&
utcOffsetDiff !== 0
"
:value="values.date_force_timezone_offset !== null"
:checked="values.date_force_timezone_offset !== null"
@input="toggleForceTimezoneOffset()"
>{{
$t(
@ -100,7 +105,7 @@
)
}}</Checkbox
>
<Checkbox v-model="values.date_show_tzinfo">{{
<Checkbox v-model="values.date_show_tzinfo" class="margin-top-1">{{
$t('fieldDateSubForm.showTimezoneLabel')
}}</Checkbox>
</div>

View file

@ -173,7 +173,7 @@
workspace.id
)
"
:value="isTableActive(table, database, operation)"
:checked="isTableActive(table, database, operation)"
@input="
toggleTable(table, database, databases, operation, $event)
"

View file

@ -1,5 +1,5 @@
<template>
<Checkbox :value="copy" :disabled="disabled" @input="input($event)">
<Checkbox :checked="copy" :disabled="disabled" @input="input($event)">
{{ $t('viewFilterTypeBoolean.selected') }}
</Checkbox>
</template>

View file

@ -2,7 +2,8 @@
<div class="control__elements">
<div v-for="option in field.select_options" :key="option.id">
<Checkbox
:value="value.findIndex((o) => o.id === option.id) !== -1"
:checked="value.findIndex((o) => o.id === option.id) !== -1"
class="margin-bottom-1"
@input=";[touch(), toggleValue(option.id, value)]"
>{{ option.value }}</Checkbox
>

View file

@ -113,7 +113,7 @@
<Checkbox
v-for="webhookEvent in webhookEventTypes"
:key="webhookEvent.type"
:value="values.events.includes(webhookEvent.type)"
:checked="values.events.includes(webhookEvent.type)"
class="webhook__type"
@input="
$event

View file

@ -0,0 +1,113 @@
import { Meta, Story, Props, Canvas } from '@storybook/addon-docs/blocks'
import { config, withDesign } from 'storybook-addon-designs'
import { action } from '@storybook/addon-actions'
import { useArgs } from '@storybook/client-api'
import Checkbox from '@baserow/modules/core/components/Checkbox'
<Meta
title="Baserow/Form/Checkbox"
component={Checkbox}
parameters={{
backgrounds: {
default: 'white',
values: [
{ name: 'white', value: '#ffffff' },
{ name: 'light', value: '#eeeeee' },
{ name: 'dark', value: '#222222' },
],
},
}}
decorators={[
withDesign,
(story, context) => {
const [_, updateArgs] = useArgs()
return story({ ...context, updateArgs })
},
]}
argTypes={{
checked: {
control: {
type: 'boolean',
options: [true, false],
},
defaultValue: false,
},
disabled: {
control: {
type: 'boolean',
options: [true, false],
},
defaultValue: false,
},
error: {
control: {
type: 'boolean',
options: [true, false],
},
defaultValue: false,
},
indeterminate: {
control: {
type: 'boolean',
options: [true, false],
},
defaultValue: false,
},
type: {
control: {
type: 'select',
options: ['regular', 'boolean'],
},
defaultValue: 'regular',
},
}}
/>
# Checbkox
The checkbox component replaces the default browser checkbox with a custom styled one. It has an additional indeterminate state.
export const Template = (args, { argTypes, updateArgs }) => ({
methods: {
action,
handleInput(checkboxValue) {
const checked = checkboxValue
updateArgs({ ...args, checked })
action('handleInput')(checkboxValue)
},
},
components: { Checkbox },
props: Object.keys(argTypes),
template: `<Checkbox v-bind="$props" @input="handleInput">${args.default}</Checkbox>`,
})
export const designConfig = {
type: 'figma',
url: 'https://www.figma.com/file/W7R2rQW7ohsZMeHRfEcPFW/Design-Library?node-id=54%3A919&mode=dev',
}
<Canvas>
<Story
name="Checkbox"
args={{
checked: true,
default: 'Label',
}}
parameters={{
design: config(designConfig),
}}
>
{Template.bind({})}
</Story>
</Canvas>
## Example
```javascript
<Checkbox>Label</Checkbox>
```
## Props
<Props of={Checkbox} />

View file

@ -0,0 +1,51 @@
import { mount } from '@vue/test-utils'
import Checkbox from '@baserow/modules/core/components/Checkbox.vue'
describe('Checkbox', () => {
it('emits an input event when clicked', async () => {
const wrapper = mount(Checkbox, {
propsData: {
checked: false,
},
})
await wrapper.trigger('click')
expect(wrapper.emitted('input')).toBeTruthy()
expect(wrapper.emitted('input')[0]).toEqual([true])
})
it('does not emit an input event when clicked and disabled', async () => {
const wrapper = mount(Checkbox, {
propsData: {
checked: false,
disabled: true,
},
})
await wrapper.trigger('click')
expect(wrapper.emitted('input')).toBeFalsy()
})
it('renders an indeterminate state when indeterminate and checkes props are true', () => {
const wrapper = mount(Checkbox, {
propsData: {
indeterminate: true,
checked: true,
},
})
expect(wrapper.find('.checkbox__tick-indeterminate').exists()).toBe(true)
})
it('renders an error state when error prop is true', () => {
const wrapper = mount(Checkbox, {
propsData: {
error: true,
},
})
expect(wrapper.find('.checkbox--error').exists()).toBe(true)
})
})

View file

@ -1490,11 +1490,63 @@ exports[`Preview exportTableModal Modal with no view 1`] = `
class="control__elements"
>
<div
class="checkbox checkbox--has-content active"
class="checkbox checkbox--checked"
>
<i
class="checkbox__checked-icon iconoir-check"
/>
<div
class="checkbox__button"
>
<svg
class="checkbox__tick"
fill="none"
height="8"
viewBox="0 0 9 8"
width="9"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#clip0_1138_66)"
>
<path
d="M1.5179 4.4821L3.18211 6.18211L7.42475 2.15368"
stroke="white"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</g>
<defs>
<clippath
id="clip0_1138_66"
>
<rect
fill="white"
height="8"
transform="translate(0.5)"
width="8"
/>
</clippath>
</defs>
</svg>
<svg
class="checkbox__tick-indeterminate"
fill="none"
height="8"
style="display: none;"
viewBox="0 0 8 8"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.5 4L6.5 4"
stroke="white"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
</div>
<label
class="checkbox__label"
@ -3031,11 +3083,63 @@ exports[`Preview exportTableModal Modal with view 1`] = `
class="control__elements"
>
<div
class="checkbox checkbox--has-content active"
class="checkbox checkbox--checked"
>
<i
class="checkbox__checked-icon iconoir-check"
/>
<div
class="checkbox__button"
>
<svg
class="checkbox__tick"
fill="none"
height="8"
viewBox="0 0 9 8"
width="9"
xmlns="http://www.w3.org/2000/svg"
>
<g
clip-path="url(#clip0_1138_66)"
>
<path
d="M1.5179 4.4821L3.18211 6.18211L7.42475 2.15368"
stroke="white"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</g>
<defs>
<clippath
id="clip0_1138_66"
>
<rect
fill="white"
height="8"
transform="translate(0.5)"
width="8"
/>
</clippath>
</defs>
</svg>
<svg
class="checkbox__tick-indeterminate"
fill="none"
height="8"
style="display: none;"
viewBox="0 0 8 8"
width="8"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1.5 4L6.5 4"
stroke="white"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
/>
</svg>
</div>
<label
class="checkbox__label"