Skip to content

Commit

Permalink
feat(VDatePicker): extract ok/cancel into separate component (#18575)
Browse files Browse the repository at this point in the history
closes #2945
  • Loading branch information
KaelWD authored Nov 2, 2023
1 parent 8a71a7d commit ec69622
Show file tree
Hide file tree
Showing 50 changed files with 372 additions and 197 deletions.
3 changes: 0 additions & 3 deletions packages/api-generator/src/locale/en/VDatePicker.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"activePicker": "Determines which picker in the date or month picker is being displayed. Allowed values: `'DATE'`, `'MONTH'`, `'YEAR'`.",
"allowedDates": "Restricts which dates can be selected.",
"calendarIcon": "The icon shown in the header when in 'input' **input-mode**.",
"cancelText": "Text used for the Cancel button.",
"dayFormat": "Allows you to customize the format of the day string that appears in the date table. Called with date (ISO 8601 **date** string) arguments.",
"displayDate": "The date displayed in the picker header.",
"eventColor": "Sets the color for event dot. It can be string (all events will have the same color) or `object` where attribute is the event date and value is boolean/color/array of colors for specified date or `function` taking date as a parameter and returning boolean/color/array of colors for that date.",
Expand All @@ -14,7 +13,6 @@
"format": "Takes a date object and returns it in a specified format.",
"header": "Text shown when no **display-date** is set.",
"headerDateFormat": "Allows you to customize the format of the month string that appears in the header of the calendar. Called with date (ISO 8601 **date** string) arguments.",
"hideActions": "Hide the picker actions.",
"inputMode": "Toggles between showing dates or inputs.",
"locale": "Sets the locale. Accepts a string with a BCP 47 language tag.",
"localeFirstDayOfYear": "Sets the day that determines the first week of the year, starting with 0 for **Sunday**. For ISO 8601 this should be 4.",
Expand All @@ -23,7 +21,6 @@
"monthFormat": "Formatting function used for displaying months in the months table. Called with date (ISO 8601 **date** string) arguments.",
"multiple": "Allow the selection of multiple dates.",
"nextIcon": "Sets the icon for next month/year button.",
"okText": "Text used for the Ok button.",
"pickerDate": "Displayed year/month.",
"prevIcon": "Sets the icon for previous month/year button.",
"range": "Allow the selection of date range.",
Expand Down
117 changes: 117 additions & 0 deletions packages/vuetify/src/labs/VConfirmEdit/VConfirmEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Components
import { VBtn } from '@/components/VBtn'

// Composables
import { useLocale } from '@/composables'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, ref, toRaw, watchEffect } from 'vue'
import { deepEqual, genericComponent, propsFactory, useRender } from '@/util'

// Types
import type { Ref, VNode } from 'vue'
import type { GenericProps } from '@/util'

export type VConfirmEditSlots<T> = {
default: {
model: Ref<T>
get actions (): VNode
}
}

export const makeVConfirmEditProps = propsFactory({
modelValue: null,
color: String,
cancelText: {
type: String,
default: '$vuetify.confirmEdit.cancel',
},
okText: {
type: String,
default: '$vuetify.confirmEdit.ok',
},
}, 'VConfirmEdit')

export const VConfirmEdit = genericComponent<new <T> (
props: {
modelValue?: T
'onUpdate:modelValue'?: (value: T) => void
'onSave'?: (value: T) => void
},
slots: VConfirmEditSlots<T>
) => GenericProps<typeof props, typeof slots>>()({
name: 'VConfirmEdit',

props: makeVConfirmEditProps(),

emits: {
cancel: () => true,
save: (value: any) => true,
'update:modelValue': (value: any) => true,
},

setup (props, { emit, slots }) {
const model = useProxiedModel(props, 'modelValue')
const internalModel = ref()
watchEffect(() => {
internalModel.value = structuredClone(toRaw(model.value))
})

const { t } = useLocale()

const isPristine = computed(() => {
return deepEqual(model.value, internalModel.value)
})

function save () {
model.value = internalModel.value
emit('save', internalModel.value)
}

function cancel () {
internalModel.value = structuredClone(toRaw(model.value))
emit('cancel')
}

let actionsUsed = false
useRender(() => {
const actions = (
<>
<VBtn
disabled={ isPristine.value }
variant="text"
color={ props.color }
onClick={ cancel }
text={ t(props.cancelText) }
/>

<VBtn
disabled={ isPristine.value }
variant="text"
color={ props.color }
onClick={ save }
text={ t(props.okText) }
/>
</>
)
return (
<>
{
slots.default?.({
model: internalModel,
get actions () {
actionsUsed = true
return actions
},
})
}

{ !actionsUsed && actions }
</>
)
})
},
})

export type VConfirmEdit = InstanceType<typeof VConfirmEdit>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// <reference types="../../../../types/cypress" />

import { VConfirmEdit } from '..'

// Utilities
import { ref } from 'vue'

// Tests
describe('VConfirmEdit', () => {
it('mirrors external updates', () => {
const externalModel = ref('foo')

cy.mount(() => (
<VConfirmEdit modelValue={ externalModel.value }>
{ ({ model }) => (
<p>{ model.value }</p>
)}
</VConfirmEdit>
)).get('p')
.should('have.text', 'foo')
.then(() => {
externalModel.value = 'bar'
})
.get('p')
.should('have.text', 'bar')
})
it.only(`doesn't mutate the original value`, () => {
const externalModel = ref(['foo'])

cy.mount(
<VConfirmEdit v-model={ externalModel.value } modelValue={ externalModel.value }>
{ ({ model }) => (
<>
<p>{ model.value.join(',') }</p>
<button data-test="push" onClick={ () => model.value.push('bar') }>Push</button>
</>
)}
</VConfirmEdit>
).get('p')
.should('have.text', 'foo')
.get('[data-test="push"]').click()
.get('p')
.should('have.text', 'foo,bar')
.then(() => {
expect(externalModel.value).to.deep.equal(['foo'])
})
cy.contains('.v-btn', 'OK').click()
cy.get('p')
.should('have.text', 'foo,bar')
.then(() => {
expect(externalModel.value).to.deep.equal(['foo', 'bar'])
})
})
it('hides actions if used from the slot', () => {
cy.mount(
<VConfirmEdit></VConfirmEdit>
).get('.v-btn').should('have.length', 2)

cy.mount(
<VConfirmEdit>
{ ({ model }) => {
void model
}}
</VConfirmEdit>
).get('.v-btn').should('have.length', 2)

cy.mount(
<VConfirmEdit>
{ ({ actions }) => {
void actions
}}
</VConfirmEdit>
).get('.v-btn').should('have.length', 0)

cy.mount(
<VConfirmEdit>
{ ({ actions }) => actions }
</VConfirmEdit>
).get('.v-btn').should('have.length', 2)
})
})
1 change: 1 addition & 0 deletions packages/vuetify/src/labs/VConfirmEdit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VConfirmEdit } from './VConfirmEdit'
52 changes: 1 addition & 51 deletions packages/vuetify/src/labs/VDatePicker/VDatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { makeVDatePickerMonthProps, VDatePickerMonth } from './VDatePickerMonth'
import { makeVDatePickerMonthsProps, VDatePickerMonths } from './VDatePickerMonths'
import { makeVDatePickerYearsProps, VDatePickerYears } from './VDatePickerYears'
import { VFadeTransition } from '@/components/transitions'
import { VBtn } from '@/components/VBtn'
import { VTextField } from '@/components/VTextField'
import { makeVPickerProps, VPicker } from '@/labs/VPicker/VPicker'

Expand Down Expand Up @@ -43,14 +42,6 @@ export const makeVDatePickerProps = propsFactory({
type: String,
default: '$edit',
},
cancelText: {
type: String,
default: '$vuetify.datePicker.cancel',
},
okText: {
type: String,
default: '$vuetify.datePicker.ok',
},
inputMode: {
type: String as PropType<'calendar' | 'keyboard'>,
default: 'calendar',
Expand All @@ -67,7 +58,6 @@ export const makeVDatePickerProps = propsFactory({
type: String,
default: '$vuetify.datePicker.header',
},
hideActions: Boolean,

...makeVDatePickerControlsProps(),
...makeVDatePickerMonthProps(),
Expand All @@ -87,8 +77,6 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
'update:year': (date: any) => true,
'update:inputMode': (date: any) => true,
'update:viewMode': (date: any) => true,
'click:cancel': () => true,
'click:save': () => true,
},

setup (props, { emit, slots }) {
Expand All @@ -109,12 +97,6 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({

return adapter.isValid(value) ? value : adapter.date()
})
const isPristine = computed(() => {
const value = adapter.date(wrapInArray(model.value)?.[0])
const ivalue = adapter.date(wrapInArray(internal.value)[0])

return adapter.isSameDay(value, ivalue)
})

const month = ref(Number(props.month ?? adapter.getMonth(adapter.startOfMonth(_model.value))))
const year = ref(Number(props.year ?? adapter.getYear(adapter.startOfYear(adapter.setMonth(_model.value, month.value)))))
Expand Down Expand Up @@ -171,16 +153,6 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
return targets
})

function onClickCancel () {
emit('click:cancel')
}

function onClickSave () {
model.value = internal.value

emit('click:save')
}

function onClickAppend () {
inputMode.value = inputMode.value === 'calendar' ? 'keyboard' : 'calendar'
}
Expand Down Expand Up @@ -233,8 +205,6 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({

isReversing.value = adapter.isBefore(before, after)

if (!props.hideActions) return

model.value = val
})

Expand Down Expand Up @@ -322,27 +292,7 @@ export const VDatePicker = genericComponent<VDatePickerSlots>()({
/>
</div>
),
actions: () => !props.hideActions ? (
slots.actions?.() ?? (
<div>
<VBtn
disabled={ isPristine.value }
variant="text"
color={ props.color }
onClick={ onClickCancel }
text={ t(props.cancelText) }
/>

<VBtn
disabled={ isPristine.value }
variant="text"
color={ props.color }
onClick={ onClickSave }
text={ t(props.okText) }
/>
</div>
)
) : undefined,
actions: slots.actions,
}}
/>
)
Expand Down

This file was deleted.

1 change: 1 addition & 0 deletions packages/vuetify/src/labs/components.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './VConfirmEdit'
export * from './VDataIterator'
export * from './VDataTable'
export * from './VDatePicker'
Expand Down
Loading

0 comments on commit ec69622

Please sign in to comment.