@planningcenter/tapestry-react 2.6.2 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Button/Button.js +12 -3
- package/dist/cjs/Button/Button.test.js +67 -14
- package/dist/cjs/Calendar/Calendar.js +30 -25
- package/dist/cjs/Combobox/ComboboxInput.js +41 -37
- package/dist/cjs/DataTable/DataTable.js +3 -2
- package/dist/cjs/DateField/DateField.js +74 -47
- package/dist/cjs/DateField/parse.js +106 -0
- package/dist/cjs/DateField/parse.test.js +46 -0
- package/dist/cjs/DateField/useArrowKeysToNavigateCalendar.js +44 -0
- package/dist/cjs/DateField/useEditableDate.js +72 -0
- package/dist/cjs/Select/Select.test.js +74 -0
- package/dist/cjs/system/colors/colors.js +18 -15
- package/dist/esm/Button/Button.js +12 -3
- package/dist/esm/Button/Button.test.js +78 -19
- package/dist/esm/Calendar/Calendar.js +30 -25
- package/dist/esm/Combobox/ComboboxInput.js +40 -37
- package/dist/esm/DataTable/DataTable.js +3 -2
- package/dist/esm/DateField/DateField.js +75 -48
- package/dist/esm/DateField/parse.js +93 -0
- package/dist/esm/DateField/parse.test.js +42 -0
- package/dist/esm/DateField/useArrowKeysToNavigateCalendar.js +36 -0
- package/dist/esm/DateField/useEditableDate.js +62 -0
- package/dist/esm/Select/Select.test.js +59 -0
- package/dist/esm/system/colors/colors.js +18 -15
- package/dist/types/Button/Button.d.ts +1 -1
- package/dist/types/DateField/DateField.d.ts +48 -0
- package/dist/types/DateField/parse.d.ts +17 -0
- package/dist/types/DateField/parse.test.d.ts +1 -0
- package/dist/types/DateField/useArrowKeysToNavigateCalendar.d.ts +8 -0
- package/dist/types/DateField/useEditableDate.d.ts +25 -0
- package/dist/types/Select/Select.test.d.ts +1 -0
- package/package.json +5 -6
- package/src/Button/Button.test.tsx +39 -1
- package/src/Button/Button.tsx +8 -3
- package/src/Calendar/Calendar.js +22 -17
- package/src/Combobox/ComboboxInput.js +76 -62
- package/src/DataTable/DataTable.js +2 -1
- package/src/DateField/DateField.mdx +15 -0
- package/src/DateField/{DateField.js → DateField.tsx} +96 -52
- package/src/DateField/parse.test.ts +76 -0
- package/src/DateField/parse.ts +92 -0
- package/src/DateField/useArrowKeysToNavigateCalendar.ts +54 -0
- package/src/DateField/useEditableDate.ts +81 -0
- package/src/Icon/Icon.mdx +12 -11
- package/src/Select/Select.test.tsx +58 -0
- package/src/system/colors/colors.js +18 -15
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { format } from 'date-fns'
|
|
2
|
+
import { parseDate, isValidDate, parseMonth } from './parse'
|
|
3
|
+
|
|
4
|
+
describe('isValidDate', () => {
|
|
5
|
+
const validDates = [
|
|
6
|
+
'jun 6, 2022',
|
|
7
|
+
'June-06-2022',
|
|
8
|
+
'6-6-2022',
|
|
9
|
+
'6/6/2022',
|
|
10
|
+
'06-06-2022',
|
|
11
|
+
'2022-06-26', // year, month, day
|
|
12
|
+
'2022/6/1', // year, month, day
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
validDates.forEach((date) => {
|
|
16
|
+
it(`returns true for "${date}"`, () => {
|
|
17
|
+
expect(isValidDate(date)).toBe(true)
|
|
18
|
+
})
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const invalidDates = [
|
|
22
|
+
'June-06-22',
|
|
23
|
+
'13-6-2022',
|
|
24
|
+
'June 6 2022',
|
|
25
|
+
'2022/6/1/1',
|
|
26
|
+
'2022-15-06',
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
invalidDates.forEach((date) => {
|
|
30
|
+
it(`returns false for "${date}"`, () => {
|
|
31
|
+
expect(isValidDate(date)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('parseDate', () => {
|
|
37
|
+
const dates = [
|
|
38
|
+
['January 31, 2022', 'January 31, 2022'],
|
|
39
|
+
['jun 9, 2022', 'June 09, 2022'],
|
|
40
|
+
['June-09-2022', 'June 09, 2022'],
|
|
41
|
+
['6-9-2022', 'June 09, 2022'],
|
|
42
|
+
['6/9/2022', 'June 09, 2022'],
|
|
43
|
+
['06-09-2022', 'June 09, 2022'],
|
|
44
|
+
['2022-06-26', 'June 26, 2022'],
|
|
45
|
+
['2022/6/1', 'June 01, 2022', 'MMMM dd, yyyy'],
|
|
46
|
+
['15/6/2022', 'June 15, 2022', 'dd/MM/YYYY'],
|
|
47
|
+
['1/6/2022', 'June 01, 2022', 'dd MMMM, yyyy'],
|
|
48
|
+
['15-aug-2023', 'August 15, 2023', 'dd MMMM, yyyy'],
|
|
49
|
+
['15 August, 2023', 'August 15, 2023', 'dd MMMM, yyyy'],
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
dates.forEach(([date, expected, dateFormat]) => {
|
|
53
|
+
it(`returns ${expected} for "${date}"`, () => {
|
|
54
|
+
expect(
|
|
55
|
+
format(parseDate({ date, format: dateFormat }), 'MMMM dd, yyyy')
|
|
56
|
+
).toEqual(expected)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('parseMonth', () => {
|
|
62
|
+
const months: [string, number][] = [
|
|
63
|
+
['jan', 1],
|
|
64
|
+
['feb', 2],
|
|
65
|
+
['December', 12],
|
|
66
|
+
['2', 2],
|
|
67
|
+
['12', 12],
|
|
68
|
+
['05', 5],
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
months.forEach(([month, expected]) => {
|
|
72
|
+
it(`returns ${expected} for "${month}"`, () => {
|
|
73
|
+
expect(parseMonth(month)).toBe(expected)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const monthDayYearFormat = /^(0?[1-9]|1[0-2]|(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*))(?:-|\/|\s)(0?[1-9]|[1-2][0-9]|3[0-1])(?:-|\/|,\s)(\d{4})$/i
|
|
2
|
+
const dayMonthYearFormat = /^(0?[1-9]|[1-2][0-9]|3[0-1])(?:-|\/|\s)(0?[1-9]|1[0-2]|(?:(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*))(?:-|\/|,\s)(\d{4})$/i
|
|
3
|
+
const yearMonthDayFormat = /^(\d{4})[-/](0?[1-9]|1[0-2])[-/](0?[1-9]|[1-2][0-9]|3[0-1])$/
|
|
4
|
+
|
|
5
|
+
interface Params {
|
|
6
|
+
/**
|
|
7
|
+
* The string we want to parse into a date object
|
|
8
|
+
*/
|
|
9
|
+
date: string
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format hint for parser
|
|
13
|
+
* Helps us know if we should parse day/month/year or month/day/year
|
|
14
|
+
*
|
|
15
|
+
* Should adhere to [date-fns spec](https://date-fns.org/v2.0.0-alpha.9/docs/format).
|
|
16
|
+
*/
|
|
17
|
+
format?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const parseDate = ({ date, format }: Params): Date | null => {
|
|
21
|
+
try {
|
|
22
|
+
const { year, month, day } = parseDateIntoObject(date, format)
|
|
23
|
+
return new Date(year, month - 1, day)
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const isValidDate = (date: string) => {
|
|
30
|
+
return monthDayYearFormat.test(date) || yearMonthDayFormat.test(date)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const parseMonth = (monthString: string): number => {
|
|
34
|
+
const months = {
|
|
35
|
+
jan: 1,
|
|
36
|
+
feb: 2,
|
|
37
|
+
mar: 3,
|
|
38
|
+
apr: 4,
|
|
39
|
+
may: 5,
|
|
40
|
+
jun: 6,
|
|
41
|
+
jul: 7,
|
|
42
|
+
aug: 8,
|
|
43
|
+
sep: 9,
|
|
44
|
+
oct: 10,
|
|
45
|
+
nov: 11,
|
|
46
|
+
dec: 12,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const normalizedString = monthString.toLowerCase().replace(/^0/, '').slice(0, 3)
|
|
50
|
+
|
|
51
|
+
if (/^\d+$/.test(normalizedString)) {
|
|
52
|
+
const month = parseInt(normalizedString, 10)
|
|
53
|
+
if (month >= 1 && month <= 12) {
|
|
54
|
+
return month
|
|
55
|
+
}
|
|
56
|
+
} else if (normalizedString in months) {
|
|
57
|
+
return months[normalizedString]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw new Error(`Invalid month string: ${monthString}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const parseDateIntoObject = (date: string, format?: string) => {
|
|
64
|
+
if (format?.match(/d.*M/) && dayMonthYearFormat.test(date)) {
|
|
65
|
+
const { 1: day, 2: month, 3: year } = dayMonthYearFormat.exec(date)
|
|
66
|
+
return {
|
|
67
|
+
year: parseInt(year, 10),
|
|
68
|
+
month: parseMonth(month),
|
|
69
|
+
day: parseInt(day, 10),
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (monthDayYearFormat.test(date)) {
|
|
74
|
+
const { 1: month, 2: day, 3: year } = monthDayYearFormat.exec(date)
|
|
75
|
+
return {
|
|
76
|
+
year: parseInt(year, 10),
|
|
77
|
+
month: parseMonth(month),
|
|
78
|
+
day: parseInt(day, 10),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (yearMonthDayFormat.test(date)) {
|
|
83
|
+
const { 1: year, 2: month, 3: day } = yearMonthDayFormat.exec(date)
|
|
84
|
+
return {
|
|
85
|
+
year: parseInt(year, 10),
|
|
86
|
+
month: parseMonth(month),
|
|
87
|
+
day: parseInt(day, 10),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
throw new Error(`Invalid date: ${date}`)
|
|
92
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useCallback } from "react"
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
date: Date
|
|
5
|
+
calendarIsOpen: boolean
|
|
6
|
+
openCalendar: () => void
|
|
7
|
+
onChange: (date: Date) => void
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const useArrowKeysToNavigateCalendar = ({
|
|
11
|
+
date,
|
|
12
|
+
calendarIsOpen,
|
|
13
|
+
openCalendar,
|
|
14
|
+
onChange,
|
|
15
|
+
}: Props) => {
|
|
16
|
+
const incrementDate = useCallback(
|
|
17
|
+
(by: number): void => {
|
|
18
|
+
if (!date) return
|
|
19
|
+
|
|
20
|
+
const newDate = new Date(date)
|
|
21
|
+
newDate.setDate(newDate.getDate() + by)
|
|
22
|
+
onChange(newDate)
|
|
23
|
+
},
|
|
24
|
+
[date, onChange]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
const handleInputKeyDown = useCallback(
|
|
28
|
+
(event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
29
|
+
if (calendarIsOpen) {
|
|
30
|
+
if (event.key === 'ArrowUp') {
|
|
31
|
+
event.preventDefault()
|
|
32
|
+
incrementDate(-7)
|
|
33
|
+
} else if (event.key === 'ArrowDown') {
|
|
34
|
+
event.preventDefault()
|
|
35
|
+
incrementDate(7)
|
|
36
|
+
} else if (event.key === 'ArrowLeft') {
|
|
37
|
+
event.preventDefault()
|
|
38
|
+
incrementDate(-1)
|
|
39
|
+
} else if (event.key === 'ArrowRight') {
|
|
40
|
+
event.preventDefault()
|
|
41
|
+
incrementDate(1)
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
if (event.key === 'ArrowDown') {
|
|
45
|
+
event.preventDefault()
|
|
46
|
+
openCalendar()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
[calendarIsOpen, openCalendar, incrementDate]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return handleInputKeyDown
|
|
54
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from "react"
|
|
2
|
+
import { format } from 'date-fns'
|
|
3
|
+
import { parseDate } from "./parse"
|
|
4
|
+
|
|
5
|
+
interface Params {
|
|
6
|
+
/**
|
|
7
|
+
* The currently selected date
|
|
8
|
+
*/
|
|
9
|
+
date: Date
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format the displayed date using date-fns [format](https://date-fns.org/v2.0.0-alpha.9/docs/format) function.
|
|
13
|
+
*/
|
|
14
|
+
dateFormat: string
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Custom function that validates date
|
|
18
|
+
*/
|
|
19
|
+
dateValidator: (date: Date) => boolean
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Called when a valid date is entered
|
|
23
|
+
*/
|
|
24
|
+
onChange: (date: Date) => void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useEditableDate = ({ date, dateFormat, dateValidator, onChange }: Params) => {
|
|
28
|
+
const [keyBuffer, setKeyBuffer] = useState<string>()
|
|
29
|
+
const [invalidKeyBuffer, setInvalidKeyBuffer] = useState(false)
|
|
30
|
+
|
|
31
|
+
const formattedDate = useMemo(() => {
|
|
32
|
+
if (keyBuffer !== undefined) {
|
|
33
|
+
return keyBuffer
|
|
34
|
+
} else {
|
|
35
|
+
return date ? format(date, dateFormat) : ''
|
|
36
|
+
}
|
|
37
|
+
}, [date, dateFormat, keyBuffer])
|
|
38
|
+
|
|
39
|
+
const setKeyBufferAndValidate = useCallback(
|
|
40
|
+
(value: string) => {
|
|
41
|
+
setKeyBuffer(value)
|
|
42
|
+
if (
|
|
43
|
+
value &&
|
|
44
|
+
!dateValidator(parseDate({ date: value, format: dateFormat }))
|
|
45
|
+
) {
|
|
46
|
+
setInvalidKeyBuffer(true)
|
|
47
|
+
} else {
|
|
48
|
+
setInvalidKeyBuffer(false)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[dateFormat, dateValidator]
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const setDate = useCallback(
|
|
55
|
+
(date: Date | string) => {
|
|
56
|
+
let newDate: Date
|
|
57
|
+
|
|
58
|
+
if (typeof date === 'string') {
|
|
59
|
+
setKeyBufferAndValidate(date)
|
|
60
|
+
newDate = parseDate({ date, format: dateFormat })
|
|
61
|
+
} else {
|
|
62
|
+
setKeyBufferAndValidate(undefined)
|
|
63
|
+
newDate = date
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (dateValidator(newDate) && onChange) onChange(newDate)
|
|
67
|
+
},
|
|
68
|
+
[onChange, setKeyBufferAndValidate, dateValidator, dateFormat]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
const clearKeyBuffer = useCallback(() => {
|
|
72
|
+
setKeyBufferAndValidate(undefined)
|
|
73
|
+
}, [])
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
formattedDate,
|
|
77
|
+
setDate,
|
|
78
|
+
clearKeyBuffer,
|
|
79
|
+
invalidKeyBuffer,
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/Icon/Icon.mdx
CHANGED
|
@@ -68,8 +68,9 @@ Preview all of the available icon sets and their icon's names from `@planningcen
|
|
|
68
68
|
|
|
69
69
|
```jsx live
|
|
70
70
|
render(() => {
|
|
71
|
-
const
|
|
72
|
-
const [
|
|
71
|
+
const iconSets = Object.keys(icons).filter(icons => icons !== "tapestry")
|
|
72
|
+
const [searchValue, setSearchValue] = React.useState('')
|
|
73
|
+
const [selectedIconSet, setSelectedIconSet] = React.useState('general')
|
|
73
74
|
|
|
74
75
|
return (
|
|
75
76
|
<StackView grow={1}>
|
|
@@ -77,12 +78,12 @@ render(() => {
|
|
|
77
78
|
<Select
|
|
78
79
|
basis={26}
|
|
79
80
|
emptyValue="Choose icon set"
|
|
80
|
-
onChange={(event) =>
|
|
81
|
-
defaultValue={
|
|
81
|
+
onChange={(event) => setSelectedIconSet(event.value)}
|
|
82
|
+
defaultValue={selectedIconSet}
|
|
82
83
|
>
|
|
83
|
-
{
|
|
84
|
-
<Select.Option key={
|
|
85
|
-
{
|
|
84
|
+
{iconSets.map((icons) => (
|
|
85
|
+
<Select.Option key={icons} value={icons}>
|
|
86
|
+
{icons}
|
|
86
87
|
</Select.Option>
|
|
87
88
|
))}
|
|
88
89
|
</Select>
|
|
@@ -91,14 +92,14 @@ render(() => {
|
|
|
91
92
|
autoFocus
|
|
92
93
|
renderLeft={<Icon name="general.search" />}
|
|
93
94
|
placeholder="Search by icon name"
|
|
94
|
-
value={
|
|
95
|
-
onChange={(e) =>
|
|
95
|
+
value={searchValue}
|
|
96
|
+
onChange={(e) => setSearchValue(e.target.value)}
|
|
96
97
|
/>
|
|
97
98
|
</StackView>
|
|
98
99
|
<TileView minCellWidth={16} spacing={4} margin={4}>
|
|
99
|
-
{matchSorter(Object.keys(icons[
|
|
100
|
+
{matchSorter(Object.keys(icons[selectedIconSet]), searchValue).map((iconName) => (
|
|
100
101
|
<StackView key={iconName} alignment="center" spacing={1}>
|
|
101
|
-
<Icon key={iconName} name={`${
|
|
102
|
+
<Icon key={iconName} name={`${selectedIconSet}.${iconName}`} size="xl" />
|
|
102
103
|
<Text fontSize={5} color="foregroundSecondary">
|
|
103
104
|
{iconName}
|
|
104
105
|
</Text>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { render, screen } from '@testing-library/react'
|
|
3
|
+
import userEvent from '@testing-library/user-event'
|
|
4
|
+
import Select from '.'
|
|
5
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider'
|
|
6
|
+
|
|
7
|
+
const people = [
|
|
8
|
+
{
|
|
9
|
+
first: 'Charlie',
|
|
10
|
+
last: 'Brown',
|
|
11
|
+
twitter: 'dancounsell',
|
|
12
|
+
active: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
first: 'Charlotte',
|
|
16
|
+
last: 'White',
|
|
17
|
+
twitter: 'mtnmissy',
|
|
18
|
+
active: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
first: 'John',
|
|
22
|
+
last: 'James',
|
|
23
|
+
twitter: 'miller',
|
|
24
|
+
active: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
first: 'Travis',
|
|
28
|
+
last: 'Arnold',
|
|
29
|
+
twitter: 'souporserious',
|
|
30
|
+
active: true,
|
|
31
|
+
},
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
const selectMock = jest.fn()
|
|
35
|
+
|
|
36
|
+
test('can click to select item from list', async () => {
|
|
37
|
+
jest.useFakeTimers()
|
|
38
|
+
|
|
39
|
+
render(
|
|
40
|
+
<ThemeProvider>
|
|
41
|
+
<Select onChange={selectMock}>
|
|
42
|
+
{people.map(p => (
|
|
43
|
+
<Select.Option value={p.twitter} key={p.twitter}>{p.first} {p.last}</Select.Option>
|
|
44
|
+
))}
|
|
45
|
+
</Select>
|
|
46
|
+
</ThemeProvider>
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
userEvent.click(screen.getByRole('button'))
|
|
50
|
+
jest.runAllTimers()
|
|
51
|
+
userEvent.click(screen.getByText('Travis Arnold'))
|
|
52
|
+
|
|
53
|
+
expect(selectMock).toHaveBeenCalledWith(
|
|
54
|
+
{ selectedValue: 'souporserious', value: 'souporserious' }
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
jest.useRealTimers()
|
|
58
|
+
})
|
|
@@ -12,27 +12,30 @@ export const palette = {
|
|
|
12
12
|
},
|
|
13
13
|
|
|
14
14
|
warning: {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
lightest: 'hsl(42, 87%, 97%)',
|
|
16
|
+
lighter: 'hsl(42, 87%, 94%)',
|
|
17
|
+
light: 'hsl(42, 87%, 90%)',
|
|
18
|
+
base: 'hsl(42, 84%, 63%)',
|
|
19
|
+
dark: 'hsl(42, 84%, 55%)',
|
|
20
|
+
darker: 'hsl(42, 84%, 49%)',
|
|
20
21
|
},
|
|
21
22
|
|
|
22
23
|
error: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
lightest: 'hsl(7, 60%, 97%)',
|
|
25
|
+
lighter: 'hsl(9, 59%, 93%)',
|
|
26
|
+
light: 'hsl(8, 60%, 85%)',
|
|
27
|
+
base: 'hsl(8, 60%, 47%)',
|
|
28
|
+
dark: 'hsl(8, 60%, 45%)',
|
|
29
|
+
darker: 'hsl(9, 61%, 43%)',
|
|
28
30
|
},
|
|
29
31
|
|
|
30
32
|
success: {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
lightest: 'hsl(93, 53%, 97%)',
|
|
34
|
+
lighter: 'hsl(95, 50%, 91%)',
|
|
35
|
+
light: 'hsl(86, 91%, 35%)',
|
|
36
|
+
base: 'hsl(86, 91%, 27%)',
|
|
37
|
+
dark: 'hsl(86, 91%, 25%)',
|
|
38
|
+
darker: 'hsl(86, 91%, 23%)',
|
|
36
39
|
},
|
|
37
40
|
|
|
38
41
|
red: [
|