mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-11 18:13:52 +02:00
Merge branch 'main' of github.com:mattermost/focalboard
This commit is contained in:
commit
38e71d8b18
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -8,8 +8,8 @@
|
|||||||
"typescriptreact"
|
"typescriptreact"
|
||||||
],
|
],
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
".vscode": true,
|
".vscode": false,
|
||||||
"**/__snapshots__": true,
|
"**/__snapshots__": false,
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"bin": true,
|
"bin": true,
|
||||||
"files": true,
|
"files": true,
|
||||||
|
@ -32,12 +32,13 @@
|
|||||||
"ContentBlock.moveDown": "Move down",
|
"ContentBlock.moveDown": "Move down",
|
||||||
"ContentBlock.moveUp": "Move up",
|
"ContentBlock.moveUp": "Move up",
|
||||||
"ContentBlock.text": "text",
|
"ContentBlock.text": "text",
|
||||||
"DashboardPage.title": "Welcome to Focalboard (Beta)!",
|
|
||||||
"DashboardPage.message": "Use Focalboard to create and track tasks for projects big and small using familiar kanban-boards, tables, and other views.",
|
"DashboardPage.message": "Use Focalboard to create and track tasks for projects big and small using familiar kanban-boards, tables, and other views.",
|
||||||
|
"DashboardPage.title": "Welcome to Focalboard (Beta)!",
|
||||||
"Dialog.closeDialog": "Close dialog",
|
"Dialog.closeDialog": "Close dialog",
|
||||||
"EditableDayPicker.today": "Today",
|
"EditableDayPicker.today": "Today",
|
||||||
"EmptyCenterPanel.no-content": "Add or select a board from the sidebar to get started.",
|
"EmptyCenterPanel.no-content": "Add or select a board from the sidebar to get started.",
|
||||||
"EmptyCenterPanel.workspace": "This is the workspace for:",
|
"EmptyCenterPanel.workspace": "This is the workspace for:",
|
||||||
|
"Error.websocket-closed": "Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.",
|
||||||
"Filter.includes": "includes",
|
"Filter.includes": "includes",
|
||||||
"Filter.is-empty": "is empty",
|
"Filter.is-empty": "is empty",
|
||||||
"Filter.is-not-empty": "is not empty",
|
"Filter.is-not-empty": "is not empty",
|
||||||
@ -90,37 +91,22 @@
|
|||||||
"Sidebar.add-board": "+ Add Board",
|
"Sidebar.add-board": "+ Add Board",
|
||||||
"Sidebar.add-template": "New template",
|
"Sidebar.add-template": "New template",
|
||||||
"Sidebar.changePassword": "Change password",
|
"Sidebar.changePassword": "Change password",
|
||||||
"Sidebar.chinese": "Traditional Chinese",
|
|
||||||
"Sidebar.dark-theme": "Dark theme",
|
|
||||||
"Sidebar.default-theme": "Default theme",
|
|
||||||
"Sidebar.delete-board": "Delete board",
|
"Sidebar.delete-board": "Delete board",
|
||||||
"Sidebar.delete-template": "Delete",
|
"Sidebar.delete-template": "Delete",
|
||||||
"Sidebar.duplicate-board": "Duplicate board",
|
"Sidebar.duplicate-board": "Duplicate board",
|
||||||
"Sidebar.dutch": "Dutch",
|
|
||||||
"Sidebar.edit-template": "Edit",
|
"Sidebar.edit-template": "Edit",
|
||||||
"Sidebar.empty-board": "Empty board",
|
"Sidebar.empty-board": "Empty board",
|
||||||
"Sidebar.english": "English",
|
|
||||||
"Sidebar.export-archive": "Export archive",
|
"Sidebar.export-archive": "Export archive",
|
||||||
"Sidebar.french": "French",
|
|
||||||
"Sidebar.german": "German",
|
|
||||||
"Sidebar.import-archive": "Import archive",
|
"Sidebar.import-archive": "Import archive",
|
||||||
"Sidebar.invite-users": "Invite Users",
|
"Sidebar.invite-users": "Invite Users",
|
||||||
"Sidebar.japanese": "Japanese",
|
|
||||||
"Sidebar.light-theme": "Light theme",
|
|
||||||
"Sidebar.logout": "Log out",
|
"Sidebar.logout": "Log out",
|
||||||
"Sidebar.no-views-in-board": "No pages inside",
|
"Sidebar.no-views-in-board": "No pages inside",
|
||||||
"Sidebar.occitan": "Occitan",
|
|
||||||
"Sidebar.random-icons": "Random icons",
|
"Sidebar.random-icons": "Random icons",
|
||||||
"Sidebar.russian": "Russian",
|
|
||||||
"Sidebar.select-a-template": "Select a template",
|
"Sidebar.select-a-template": "Select a template",
|
||||||
"Sidebar.set-language": "Set language",
|
"Sidebar.set-language": "Set language",
|
||||||
"Sidebar.set-theme": "Set theme",
|
"Sidebar.set-theme": "Set theme",
|
||||||
"Sidebar.settings": "Settings",
|
"Sidebar.settings": "Settings",
|
||||||
"Sidebar.simplified-chinese": "Simplified Chinese",
|
|
||||||
"Sidebar.spanish": "Spanish",
|
|
||||||
"Sidebar.system-theme": "System theme",
|
|
||||||
"Sidebar.template-from-board": "New template from board",
|
"Sidebar.template-from-board": "New template from board",
|
||||||
"Sidebar.turkish": "Turkish",
|
|
||||||
"Sidebar.untitled": "Untitled",
|
"Sidebar.untitled": "Untitled",
|
||||||
"Sidebar.untitled-board": "(Untitled Board)",
|
"Sidebar.untitled-board": "(Untitled Board)",
|
||||||
"Sidebar.untitled-view": "(Untitled View)",
|
"Sidebar.untitled-view": "(Untitled View)",
|
||||||
@ -147,6 +133,7 @@
|
|||||||
"ViewHeader.delete-template": "Delete",
|
"ViewHeader.delete-template": "Delete",
|
||||||
"ViewHeader.edit-template": "Edit",
|
"ViewHeader.edit-template": "Edit",
|
||||||
"ViewHeader.empty-card": "Empty card",
|
"ViewHeader.empty-card": "Empty card",
|
||||||
|
"ViewHeader.export-board-archive": "Export board archive",
|
||||||
"ViewHeader.export-complete": "Export complete!",
|
"ViewHeader.export-complete": "Export complete!",
|
||||||
"ViewHeader.export-csv": "Export to CSV",
|
"ViewHeader.export-csv": "Export to CSV",
|
||||||
"ViewHeader.export-failed": "Export failed!",
|
"ViewHeader.export-failed": "Export failed!",
|
||||||
@ -160,11 +147,11 @@
|
|||||||
"ViewHeader.share-board": "Share board",
|
"ViewHeader.share-board": "Share board",
|
||||||
"ViewHeader.sort": "Sort",
|
"ViewHeader.sort": "Sort",
|
||||||
"ViewHeader.untitled": "Untitled",
|
"ViewHeader.untitled": "Untitled",
|
||||||
"ViewTitle.hide-description": "Hide description",
|
"ViewTitle.hide-description": "hide description",
|
||||||
"ViewTitle.pick-icon": "Pick icon",
|
"ViewTitle.pick-icon": "Pick icon",
|
||||||
"ViewTitle.random-icon": "Random",
|
"ViewTitle.random-icon": "Random",
|
||||||
"ViewTitle.remove-icon": "Remove icon",
|
"ViewTitle.remove-icon": "Remove icon",
|
||||||
"ViewTitle.show-description": "Show description",
|
"ViewTitle.show-description": "show description",
|
||||||
"ViewTitle.untitled-board": "Untitled board",
|
"ViewTitle.untitled-board": "Untitled board",
|
||||||
"Workspace.editing-board-template": "You're editing a board template",
|
"Workspace.editing-board-template": "You're editing a board template",
|
||||||
"default-properties.title": "Title",
|
"default-properties.title": "Title",
|
||||||
|
@ -0,0 +1,103 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange cancel set via text input 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
June 15 -> June 20
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange handle clear 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
June 15
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange returns default correctly 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
Empty
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange returns local correctly - es local 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
15 de junio
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange set via text input 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
June 15 -> June 20
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`components/properties/dateRange set via text input, es locale 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange "
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button "
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
15 de junio -> 20 de junio
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
155
webapp/src/components/properties/dateRange/dateRange.scss
Normal file
155
webapp/src/components/properties/dateRange/dateRange.scss
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
.DateRange {
|
||||||
|
.octo-propertyvalue {
|
||||||
|
white-space: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputContainer {
|
||||||
|
display: flex;
|
||||||
|
.Editable {
|
||||||
|
width: 50% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
cursor: text;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-height: 24px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 2px;
|
||||||
|
&.active {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
&::placeholder {
|
||||||
|
color: rgba(var(--body-color), 0.4);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
&.error {
|
||||||
|
border: 1px solid var(--error-color);
|
||||||
|
border-radius: var(--default-rad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Modal{
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
margin-bottom: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Button{
|
||||||
|
width: 100%;
|
||||||
|
justify-content: left;
|
||||||
|
&:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.menu-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px 0px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(var(--button-bg), 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .menu-name {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .SubmenuTriangleIcon {
|
||||||
|
fill: rgba(var(--body-color), 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .Icon {
|
||||||
|
opacity: 0.56;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .IconButton .Icon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.DayPicker {
|
||||||
|
}
|
||||||
|
|
||||||
|
.DayPickerInput-Overlay {
|
||||||
|
background-color: rgba(var(--main-bg));
|
||||||
|
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1),0 4px 11px hsla(0, 0%, 0%, 0.1);
|
||||||
|
line-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.DayPicker-Month {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 1em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.DayPicker-Body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.DayPicker-WeekdaysRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.DayPicker-Weekday {
|
||||||
|
display: block;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.DayPicker-Week {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.DayPicker-Day {
|
||||||
|
border-radius: unset;
|
||||||
|
display: block;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.DayPicker-Day--today {
|
||||||
|
color: #c74655;
|
||||||
|
}
|
||||||
|
.DayPicker-Day--start {
|
||||||
|
border-top-left-radius: 50% !important;
|
||||||
|
border-bottom-left-radius: 50% !important;
|
||||||
|
}
|
||||||
|
.DayPicker-Day--end {
|
||||||
|
border-top-right-radius: 50% !important;
|
||||||
|
border-bottom-right-radius: 50% !important;
|
||||||
|
}
|
||||||
|
.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside) {
|
||||||
|
background-color: rgb(var(--button-bg));;
|
||||||
|
color: rgba(var(--button-fg), 0.5);
|
||||||
|
}
|
||||||
|
.DayPicker-Day--selected:not(.DayPicker-Day--start):not(.DayPicker-Day--end):not(.DayPicker-Day--outside) {
|
||||||
|
color: rgb(var(--button-bg));;
|
||||||
|
background-color: rgba(var(--button-bg), 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.DayPicker:not(.DayPicker--interactionDisabled)
|
||||||
|
.DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover {
|
||||||
|
background-color: rgba(var(--body-color), 0.2);
|
||||||
|
}
|
||||||
|
}
|
245
webapp/src/components/properties/dateRange/dateRange.test.tsx
Normal file
245
webapp/src/components/properties/dateRange/dateRange.test.tsx
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
import {render} from '@testing-library/react'
|
||||||
|
import userEvent from '@testing-library/user-event'
|
||||||
|
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
import {IntlProvider} from 'react-intl'
|
||||||
|
|
||||||
|
import DateRange from '../dateRange/dateRange'
|
||||||
|
|
||||||
|
const wrapIntl = (children: any) => <IntlProvider locale='en'>{children}</IntlProvider>
|
||||||
|
|
||||||
|
// create Dates for specific days for this year.
|
||||||
|
const June15 = new Date(Date.UTC(new Date().getFullYear(), 5, 15, 12))
|
||||||
|
const June15Local = new Date(new Date().getFullYear(), 5, 15, 12)
|
||||||
|
const June20 = new Date(Date.UTC(new Date().getFullYear(), 5, 20, 12))
|
||||||
|
|
||||||
|
describe('components/properties/dateRange', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Quick fix to disregard console error when unmounting a component
|
||||||
|
console.error = jest.fn()
|
||||||
|
document.execCommand = jest.fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns default correctly', () => {
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={''}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns local correctly - es local', () => {
|
||||||
|
const component = (
|
||||||
|
<IntlProvider locale='es'>
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={June15Local.getTime().toString()}
|
||||||
|
onChange={jest.fn()}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, getByText} = render(component)
|
||||||
|
const input = getByText('15 de junio')
|
||||||
|
expect(input).not.toBeNull()
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles calendar click event', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={''}
|
||||||
|
onChange={callback}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const date = new Date()
|
||||||
|
const fifteenth = Date.UTC(date.getFullYear(), date.getMonth(), 15, 12)
|
||||||
|
|
||||||
|
const {getByText, getByTitle} = render(component)
|
||||||
|
const dayDisplay = getByText('Empty')
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
const day = getByText('15')
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
userEvent.click(day)
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
const rObject = {from: fifteenth}
|
||||||
|
expect(callback).toHaveBeenCalledWith(JSON.stringify(rObject))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handles setting range', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={''}
|
||||||
|
onChange={callback}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
const {getByText, getByTitle} = render(component)
|
||||||
|
const dayDisplay = getByText('Empty')
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
// select start date
|
||||||
|
const date = new Date()
|
||||||
|
const fifteenth = Date.UTC(date.getFullYear(), date.getMonth(), 15, 12)
|
||||||
|
const start = getByText('15')
|
||||||
|
userEvent.click(start)
|
||||||
|
|
||||||
|
// create range
|
||||||
|
const endDate = getByText('End date')
|
||||||
|
userEvent.click(endDate)
|
||||||
|
|
||||||
|
const twentieth = Date.UTC(date.getFullYear(), date.getMonth(), 20, 12)
|
||||||
|
|
||||||
|
const end = getByText('20')
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
userEvent.click(end)
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
const rObject = {from: fifteenth, to: twentieth}
|
||||||
|
expect(callback).toHaveBeenCalledWith(JSON.stringify(rObject))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('handle clear', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={June15Local.getTime().toString()}
|
||||||
|
onChange={callback}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, getByText, getByTitle} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
const dayDisplay = getByText('June 15')
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
const clear = getByText('Clear')
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
userEvent.click(clear)
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
expect(callback).toHaveBeenCalledWith('')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('set via text input', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
|
onChange={callback}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, getByRole, getByTitle, getByDisplayValue} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
const dayDisplay = getByRole('button', {name: 'June 15 -> June 20'})
|
||||||
|
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
const fromInput = getByDisplayValue('June 15')
|
||||||
|
const toInput = getByDisplayValue('June 20')
|
||||||
|
|
||||||
|
userEvent.type(fromInput, '{selectall}{delay}07/15/2021{enter}')
|
||||||
|
userEvent.type(toInput, '{selectall}{delay}07/20/2021{enter}')
|
||||||
|
|
||||||
|
const July15 = new Date(Date.UTC(2021, 6, 15, 12))
|
||||||
|
const July20 = new Date(Date.UTC(2021, 6, 20, 12))
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
// {from: '2021-07-15', to: '2021-07-20'}
|
||||||
|
const retVal = '{"from":' + July15.getTime().toString() + ',"to":' + July20.getTime().toString() + '}'
|
||||||
|
expect(callback).toHaveBeenCalledWith(retVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('set via text input, es locale', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
|
||||||
|
const component = (
|
||||||
|
<IntlProvider locale='es'>
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
|
onChange={callback}
|
||||||
|
/>
|
||||||
|
</IntlProvider>
|
||||||
|
)
|
||||||
|
const {container, getByRole, getByTitle, getByDisplayValue} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
const dayDisplay = getByRole('button', {name: '15 de junio -> 20 de junio'})
|
||||||
|
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
const fromInput = getByDisplayValue('15 de junio')
|
||||||
|
const toInput = getByDisplayValue('20 de junio')
|
||||||
|
|
||||||
|
userEvent.type(fromInput, '{selectall}15/07/2021{enter}')
|
||||||
|
userEvent.type(toInput, '{selectall}20/07/2021{enter}')
|
||||||
|
|
||||||
|
const July15 = new Date(Date.UTC(2021, 6, 15, 12))
|
||||||
|
const July20 = new Date(Date.UTC(2021, 6, 20, 12))
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
// {from: '2021-07-15', to: '2021-07-20'}
|
||||||
|
const retVal = '{"from":' + July15.getTime().toString() + ',"to":' + July20.getTime().toString() + '}'
|
||||||
|
expect(callback).toHaveBeenCalledWith(retVal)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cancel set via text input', () => {
|
||||||
|
const callback = jest.fn()
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateRange
|
||||||
|
className='octo-propertyvalue'
|
||||||
|
value={'{"from": ' + June15.getTime().toString() + ',"to": ' + June20.getTime().toString() + '}'}
|
||||||
|
onChange={callback}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, getByRole, getByTitle, getByDisplayValue} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
|
||||||
|
// open modal
|
||||||
|
const dayDisplay = getByRole('button', {name: 'June 15 -> June 20'})
|
||||||
|
userEvent.click(dayDisplay)
|
||||||
|
|
||||||
|
const fromInput = getByDisplayValue('June 15')
|
||||||
|
const toInput = getByDisplayValue('June 20')
|
||||||
|
userEvent.type(fromInput, '{selectall}07/15/2021{delay}{esc}')
|
||||||
|
userEvent.type(toInput, '{selectall}07/20/2021{delay}{esc}')
|
||||||
|
|
||||||
|
const modal = getByTitle('Close').children[0]
|
||||||
|
userEvent.click(modal)
|
||||||
|
|
||||||
|
// const retVal = {from: '2021-06-15', to: '2021-06-20'}
|
||||||
|
const retVal = '{"from":' + June15.getTime().toString() + ',"to":' + June20.getTime().toString() + '}'
|
||||||
|
expect(callback).toHaveBeenCalledWith(retVal)
|
||||||
|
})
|
||||||
|
})
|
266
webapp/src/components/properties/dateRange/dateRange.tsx
Normal file
266
webapp/src/components/properties/dateRange/dateRange.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import React, {useState} from 'react'
|
||||||
|
import {useIntl} from 'react-intl'
|
||||||
|
import {DateUtils} from 'react-day-picker'
|
||||||
|
import MomentLocaleUtils from 'react-day-picker/moment'
|
||||||
|
import DayPicker from 'react-day-picker/DayPicker'
|
||||||
|
|
||||||
|
import moment from 'moment'
|
||||||
|
|
||||||
|
import Editable from '../../../widgets/editable'
|
||||||
|
import SwitchOption from '../../../widgets/menu/switchOption'
|
||||||
|
import Button from '../../../widgets/buttons/button'
|
||||||
|
|
||||||
|
import Modal from '../../../components/modal'
|
||||||
|
import ModalWrapper from '../../../components/modalWrapper'
|
||||||
|
|
||||||
|
import 'react-day-picker/lib/style.css'
|
||||||
|
import './dateRange.scss'
|
||||||
|
import {Utils} from '../../../utils'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className: string
|
||||||
|
value: string
|
||||||
|
onChange: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type DateProperty = {
|
||||||
|
from?: number
|
||||||
|
to?: number
|
||||||
|
includeTime?: boolean
|
||||||
|
timeZone?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedLocales: Record<string, any> = {}
|
||||||
|
|
||||||
|
function DateRange(props: Props): JSX.Element {
|
||||||
|
const {className, value, onChange} = props
|
||||||
|
const intl = useIntl()
|
||||||
|
const timeZoneOffset = new Date().getTimezoneOffset() * 60 * 1000
|
||||||
|
|
||||||
|
const getDisplayDate = (date: Date | null | undefined) => {
|
||||||
|
let displayDate = ''
|
||||||
|
if (date) {
|
||||||
|
displayDate = Utils.displayDate(date, intl)
|
||||||
|
}
|
||||||
|
return displayDate
|
||||||
|
}
|
||||||
|
|
||||||
|
const createDatePropertyFromString = (initialValue: string) => {
|
||||||
|
let dateProperty: DateProperty = {}
|
||||||
|
if (initialValue) {
|
||||||
|
const singleDate = new Date(Number(initialValue))
|
||||||
|
if (singleDate && DateUtils.isDate(singleDate)) {
|
||||||
|
dateProperty.from = singleDate.getTime()
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
dateProperty = JSON.parse(initialValue)
|
||||||
|
} catch {
|
||||||
|
//Don't do anything, return empty dateProperty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dateProperty
|
||||||
|
}
|
||||||
|
|
||||||
|
const [dateProperty, setDateProperty] = useState<DateProperty>(createDatePropertyFromString(value as string))
|
||||||
|
const [showDialog, setShowDialog] = useState(false)
|
||||||
|
|
||||||
|
// Keep dateProperty as UTC,
|
||||||
|
// dateFrom / dateTo will need converted to local time, to ensure date stays consistent
|
||||||
|
// dateFrom / dateTo will be used for input and calendar dates
|
||||||
|
const dateFrom = dateProperty.from ? new Date(dateProperty.from + (dateProperty.includeTime ? 0 : timeZoneOffset)) : undefined
|
||||||
|
const dateTo = dateProperty.to ? new Date(dateProperty.to + (dateProperty.includeTime ? 0 : timeZoneOffset)) : undefined
|
||||||
|
const [fromInput, setFromInput] = useState<string>(getDisplayDate(dateFrom))
|
||||||
|
const [toInput, setToInput] = useState<string>(getDisplayDate(dateTo))
|
||||||
|
|
||||||
|
const isRange = dateTo !== undefined
|
||||||
|
|
||||||
|
const locale = intl.locale.toLowerCase()
|
||||||
|
if (locale && locale !== 'en' && !loadedLocales[locale]) {
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
loadedLocales[locale] = require(`moment/locale/${locale}`)
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDayClick = (day: Date) => {
|
||||||
|
const range : DateProperty = {}
|
||||||
|
if (isRange) {
|
||||||
|
const newRange = DateUtils.addDayToRange(day, {from: dateFrom, to: dateTo})
|
||||||
|
range.from = newRange.from?.getTime()
|
||||||
|
range.to = newRange.to?.getTime()
|
||||||
|
} else {
|
||||||
|
range.from = day.getTime()
|
||||||
|
range.to = undefined
|
||||||
|
}
|
||||||
|
saveRangeValue(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onRangeClick = () => {
|
||||||
|
let range : DateProperty = {
|
||||||
|
from: dateFrom?.getTime(),
|
||||||
|
to: dateFrom?.getTime(),
|
||||||
|
}
|
||||||
|
if (isRange) {
|
||||||
|
range = ({
|
||||||
|
from: dateFrom?.getTime(),
|
||||||
|
to: undefined,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
saveRangeValue(range)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClear = () => {
|
||||||
|
saveRangeValue({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveRangeValue = (range: DateProperty) => {
|
||||||
|
const rangeUTC = {...range}
|
||||||
|
if (rangeUTC.from) {
|
||||||
|
rangeUTC.from -= dateProperty.includeTime ? 0 : timeZoneOffset
|
||||||
|
}
|
||||||
|
if (rangeUTC.to) {
|
||||||
|
rangeUTC.to -= dateProperty.includeTime ? 0 : timeZoneOffset
|
||||||
|
}
|
||||||
|
|
||||||
|
setDateProperty(rangeUTC)
|
||||||
|
setFromInput(getDisplayDate(range.from ? new Date(range.from) : undefined))
|
||||||
|
setToInput(getDisplayDate(range.to ? new Date(range.to) : undefined))
|
||||||
|
}
|
||||||
|
|
||||||
|
let displayValue = ''
|
||||||
|
if (dateFrom) {
|
||||||
|
displayValue = getDisplayDate(dateFrom)
|
||||||
|
}
|
||||||
|
if (dateTo) {
|
||||||
|
displayValue += ' -> ' + getDisplayDate(dateTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onClose = () => {
|
||||||
|
// not actually setting here,
|
||||||
|
// but using to retreive the current state
|
||||||
|
setDateProperty((current) => {
|
||||||
|
if (current && current.from) {
|
||||||
|
onChange(JSON.stringify(current))
|
||||||
|
} else {
|
||||||
|
onChange('')
|
||||||
|
}
|
||||||
|
return {...current}
|
||||||
|
})
|
||||||
|
setShowDialog(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={'DateRange '}>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowDialog(true)}
|
||||||
|
>
|
||||||
|
{displayValue || intl.formatMessage({id: 'DateRange.empty', defaultMessage: 'Empty'})}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{showDialog &&
|
||||||
|
<ModalWrapper>
|
||||||
|
<Modal
|
||||||
|
onClose={() => onClose()}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={className + '-overlayWrapper'}
|
||||||
|
>
|
||||||
|
<div className={className + '-overlay'}>
|
||||||
|
<div className={'inputContainer'}>
|
||||||
|
<Editable
|
||||||
|
value={fromInput}
|
||||||
|
placeholderText={moment.localeData(locale).longDateFormat('L')}
|
||||||
|
onFocus={() => {
|
||||||
|
if (dateFrom) {
|
||||||
|
return setFromInput(Utils.inputDate(dateFrom, intl))
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}}
|
||||||
|
onChange={setFromInput}
|
||||||
|
onSave={() => {
|
||||||
|
const newDate = MomentLocaleUtils.parseDate(fromInput, 'L', intl.locale)
|
||||||
|
if (newDate && DateUtils.isDate(newDate)) {
|
||||||
|
newDate.setHours(12)
|
||||||
|
const range : DateProperty = {
|
||||||
|
from: newDate.getTime(),
|
||||||
|
to: dateTo?.getTime(),
|
||||||
|
}
|
||||||
|
saveRangeValue(range)
|
||||||
|
} else {
|
||||||
|
setFromInput(getDisplayDate(dateFrom))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setFromInput(getDisplayDate(dateFrom))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{dateTo &&
|
||||||
|
<Editable
|
||||||
|
value={toInput}
|
||||||
|
placeholderText={moment.localeData(locale).longDateFormat('L')}
|
||||||
|
onFocus={() => {
|
||||||
|
if (dateTo) {
|
||||||
|
return setToInput(Utils.inputDate(dateTo, intl))
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}}
|
||||||
|
onChange={setToInput}
|
||||||
|
onSave={() => {
|
||||||
|
const newDate = MomentLocaleUtils.parseDate(toInput, 'L', intl.locale)
|
||||||
|
if (newDate && DateUtils.isDate(newDate)) {
|
||||||
|
newDate.setHours(12)
|
||||||
|
const range : DateProperty = {
|
||||||
|
from: dateFrom?.getTime(),
|
||||||
|
to: newDate.getTime(),
|
||||||
|
}
|
||||||
|
saveRangeValue(range)
|
||||||
|
} else {
|
||||||
|
setToInput(getDisplayDate(dateTo))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setToInput(getDisplayDate(dateTo))
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<DayPicker
|
||||||
|
onDayClick={handleDayClick}
|
||||||
|
initialMonth={dateFrom || new Date()}
|
||||||
|
showOutsideDays={true}
|
||||||
|
locale={locale}
|
||||||
|
localeUtils={MomentLocaleUtils}
|
||||||
|
todayButton={intl.formatMessage({id: 'DateRange.today', defaultMessage: 'Today'})}
|
||||||
|
selectedDays={[dateFrom, dateTo ? {from: dateFrom, to: dateTo} : {from: dateFrom, to: dateFrom}]}
|
||||||
|
modifiers={dateTo ? {start: dateFrom, end: dateTo} : {start: dateFrom, end: dateFrom}}
|
||||||
|
/>
|
||||||
|
<hr/>
|
||||||
|
<SwitchOption
|
||||||
|
key={'EndDateOn'}
|
||||||
|
id={'EndDateOn'}
|
||||||
|
name={intl.formatMessage({id: 'DateRange.endDate', defaultMessage: 'End date'})}
|
||||||
|
isOn={isRange}
|
||||||
|
onClick={onRangeClick}
|
||||||
|
/>
|
||||||
|
<hr/>
|
||||||
|
<div
|
||||||
|
className='MenuOption menu-option'
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={onClear}
|
||||||
|
>
|
||||||
|
{intl.formatMessage({id: 'DateRange.clear', defaultMessage: 'Clear'})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</ModalWrapper>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DateRange
|
@ -16,7 +16,6 @@ import ValueSelector from '../widgets/valueSelector'
|
|||||||
|
|
||||||
import Label from '../widgets/label'
|
import Label from '../widgets/label'
|
||||||
|
|
||||||
import EditableDayPicker from '../widgets/editableDayPicker'
|
|
||||||
import Switch from '../widgets/switch'
|
import Switch from '../widgets/switch'
|
||||||
import IconButton from '../widgets/buttons/iconButton'
|
import IconButton from '../widgets/buttons/iconButton'
|
||||||
import CloseIcon from '../widgets/icons/close'
|
import CloseIcon from '../widgets/icons/close'
|
||||||
@ -28,6 +27,7 @@ import LastModifiedBy from './properties/lastModifiedBy/lastModifiedBy'
|
|||||||
import LastModifiedAt from './properties/lastModifiedAt/lastModifiedAt'
|
import LastModifiedAt from './properties/lastModifiedAt/lastModifiedAt'
|
||||||
import CreatedAt from './properties/createdAt/createdAt'
|
import CreatedAt from './properties/createdAt/createdAt'
|
||||||
import CreatedBy from './properties/createdBy/createdBy'
|
import CreatedBy from './properties/createdBy/createdBy'
|
||||||
|
import DateRange from './properties/dateRange/dateRange'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
board: Board
|
board: Board
|
||||||
@ -198,7 +198,7 @@ const PropertyValueElement = (props:Props): JSX.Element => {
|
|||||||
return <div className='octo-propertyvalue'>{displayValue}</div>
|
return <div className='octo-propertyvalue'>{displayValue}</div>
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<EditableDayPicker
|
<DateRange
|
||||||
className='octo-propertyvalue'
|
className='octo-propertyvalue'
|
||||||
value={value as string}
|
value={value as string}
|
||||||
onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)}
|
onChange={(newValue) => mutator.changePropertyValue(card, propertyTemplate.id, newValue)}
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
import {IntlShape} from 'react-intl'
|
import {IntlShape} from 'react-intl'
|
||||||
|
|
||||||
|
import {DateUtils} from 'react-day-picker'
|
||||||
|
|
||||||
import {Block, createBlock} from './blocks/block'
|
import {Block, createBlock} from './blocks/block'
|
||||||
import {IPropertyTemplate, createBoard} from './blocks/board'
|
import {IPropertyTemplate, createBoard} from './blocks/board'
|
||||||
import {BoardView, createBoardView} from './blocks/boardView'
|
import {BoardView, createBoardView} from './blocks/boardView'
|
||||||
@ -50,7 +52,23 @@ class OctoUtils {
|
|||||||
}
|
}
|
||||||
case 'date': {
|
case 'date': {
|
||||||
if (propertyValue) {
|
if (propertyValue) {
|
||||||
|
const singleDate = new Date(parseInt(propertyValue as string, 10))
|
||||||
|
if (singleDate && DateUtils.isDate(singleDate)) {
|
||||||
displayValue = Utils.displayDate(new Date(parseInt(propertyValue as string, 10)), intl)
|
displayValue = Utils.displayDate(new Date(parseInt(propertyValue as string, 10)), intl)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const dateValue = JSON.parse(propertyValue as string)
|
||||||
|
if (dateValue.from) {
|
||||||
|
displayValue = Utils.displayDate(new Date(dateValue.from), intl)
|
||||||
|
}
|
||||||
|
if (dateValue.to) {
|
||||||
|
displayValue += ' -> '
|
||||||
|
displayValue += Utils.displayDate(new Date(dateValue.to), intl)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,21 @@ describe('utils', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('input date', () => {
|
||||||
|
const currentYear = new Date().getFullYear()
|
||||||
|
const date = new Date(currentYear, 6, 9)
|
||||||
|
|
||||||
|
it('should show mm/dd/yyyy for current year', () => {
|
||||||
|
const intl = createIntl({locale: 'en-us'})
|
||||||
|
expect(Utils.inputDate(date, intl)).toBe(`07/09/${currentYear}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should show dd/mm/yyyy for current year, es local', () => {
|
||||||
|
const intl = createIntl({locale: 'es-es'})
|
||||||
|
expect(Utils.inputDate(date, intl)).toBe(`09/07/${currentYear}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('display date and time', () => {
|
describe('display date and time', () => {
|
||||||
const intl = createIntl({locale: 'en-us'})
|
const intl = createIntl({locale: 'en-us'})
|
||||||
|
|
||||||
|
@ -159,7 +159,6 @@ class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Date and Time
|
// Date and Time
|
||||||
|
|
||||||
private static yearOption(date: Date) {
|
private static yearOption(date: Date) {
|
||||||
const isCurrentYear = date.getFullYear() === new Date().getFullYear()
|
const isCurrentYear = date.getFullYear() === new Date().getFullYear()
|
||||||
return isCurrentYear ? undefined : 'numeric'
|
return isCurrentYear ? undefined : 'numeric'
|
||||||
@ -173,6 +172,14 @@ class Utils {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inputDate(date: Date, intl: IntlShape): string {
|
||||||
|
return intl.formatDate(date, {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
static displayDateTime(date: Date, intl: IntlShape): string {
|
static displayDateTime(date: Date, intl: IntlShape): string {
|
||||||
return intl.formatDate(date, {
|
return intl.formatDate(date, {
|
||||||
year: Utils.yearOption(date),
|
year: Utils.yearOption(date),
|
||||||
|
@ -16,6 +16,7 @@ export type EditableProps = {
|
|||||||
validator?: (value: string) => boolean
|
validator?: (value: string) => boolean
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
onSave?: (saveType: 'onEnter'|'onEsc'|'onBlur') => void
|
onSave?: (saveType: 'onEnter'|'onEsc'|'onBlur') => void
|
||||||
|
onFocus?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Focusable = {
|
export type Focusable = {
|
||||||
@ -33,7 +34,8 @@ export type ElementProps = {
|
|||||||
onBlur: () => void,
|
onBlur: () => void,
|
||||||
onKeyDown: (e: React.KeyboardEvent<ElementType>) => void,
|
onKeyDown: (e: React.KeyboardEvent<ElementType>) => void,
|
||||||
readOnly?: boolean,
|
readOnly?: boolean,
|
||||||
spellCheck?: boolean
|
spellCheck?: boolean,
|
||||||
|
onFocus?: () => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEditable(
|
export function useEditable(
|
||||||
@ -109,6 +111,7 @@ export function useEditable(
|
|||||||
},
|
},
|
||||||
readOnly: readonly,
|
readOnly: readonly,
|
||||||
spellCheck: props.spellCheck,
|
spellCheck: props.spellCheck,
|
||||||
|
onFocus: props.onFocus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
@ -27,7 +26,6 @@
|
|||||||
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1),0 4px 11px hsla(0, 0%, 0%, 0.1);
|
box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1),0 4px 11px hsla(0, 0%, 0%, 0.1);
|
||||||
line-height: 100%;
|
line-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.DayPicker-Day--today {
|
.DayPicker-Day--today {
|
||||||
color: #c74655;
|
color: #c74655;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user