@operato/form 1.0.0-beta.0 → 1.0.0-beta.11

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/demo/index.html +68 -2
  3. package/dist/src/filters/filter-checkbox.d.ts +3 -0
  4. package/dist/src/filters/filter-checkbox.js +26 -0
  5. package/dist/src/filters/filter-checkbox.js.map +1 -0
  6. package/dist/src/filters/filter-input.d.ts +2 -0
  7. package/dist/src/filters/filter-input.js +24 -0
  8. package/dist/src/filters/filter-input.js.map +1 -0
  9. package/dist/src/filters/filter-range-date.d.ts +2 -0
  10. package/dist/src/filters/filter-range-date.js +54 -0
  11. package/dist/src/filters/filter-range-date.js.map +1 -0
  12. package/dist/src/filters/filter-range-number.d.ts +2 -0
  13. package/dist/src/filters/filter-range-number.js +53 -0
  14. package/dist/src/filters/filter-range-number.js.map +1 -0
  15. package/dist/src/filters/filter-select.d.ts +3 -0
  16. package/dist/src/filters/filter-select.js +33 -0
  17. package/dist/src/filters/filter-select.js.map +1 -0
  18. package/dist/src/filters/filter-styles.d.ts +1 -0
  19. package/dist/src/filters/filter-styles.js +87 -0
  20. package/dist/src/filters/filter-styles.js.map +1 -0
  21. package/dist/src/filters/index.d.ts +7 -0
  22. package/dist/src/filters/index.js +8 -0
  23. package/dist/src/filters/index.js.map +1 -0
  24. package/dist/src/filters/ox-filters-form-base.d.ts +25 -0
  25. package/dist/src/filters/ox-filters-form-base.js +238 -0
  26. package/dist/src/filters/ox-filters-form-base.js.map +1 -0
  27. package/dist/src/filters/registry.d.ts +7 -0
  28. package/dist/src/filters/registry.js +43 -0
  29. package/dist/src/filters/registry.js.map +1 -0
  30. package/dist/src/filters/types.d.ts +17 -0
  31. package/dist/src/filters/types.js +2 -0
  32. package/dist/src/filters/types.js.map +1 -0
  33. package/dist/src/index.d.ts +1 -0
  34. package/dist/src/index.js +1 -0
  35. package/dist/src/index.js.map +1 -1
  36. package/dist/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +6 -4
  38. package/src/filters/filter-checkbox.ts +31 -0
  39. package/src/filters/filter-input.ts +29 -0
  40. package/src/filters/filter-range-date.ts +64 -0
  41. package/src/filters/filter-range-number.ts +62 -0
  42. package/src/filters/filter-select.ts +47 -0
  43. package/src/filters/filter-styles.ts +87 -0
  44. package/src/filters/index.ts +9 -0
  45. package/src/filters/ox-filters-form-base.ts +261 -0
  46. package/src/filters/registry.ts +51 -0
  47. package/src/filters/types.ts +47 -0
  48. package/src/index.ts +1 -0
  49. package/themes/app-theme.css +142 -0
  50. /package/{src/themes → themes}/form-theme.css +0 -0
@@ -0,0 +1,47 @@
1
+ import '@operato/input/ox-checkbox.js'
2
+
3
+ import { html } from 'lit-html'
4
+
5
+ import { FilterSelectRenderer } from './types.js'
6
+
7
+ export const FilterSelect: FilterSelectRenderer = (filter, value, owner) => {
8
+ /* value는 filters-form이나 grid-header에서 처리되므로 이 곳에서는 무시한다. */
9
+ var { name, type, operator, options = [] } = filter
10
+
11
+ if (typeof options === 'function') {
12
+ options = options.call(filter, owner)
13
+ }
14
+
15
+ options = options.map((option: any) => {
16
+ switch (typeof option) {
17
+ case 'string':
18
+ return {
19
+ display: option,
20
+ value: option
21
+ }
22
+ case 'object':
23
+ return {
24
+ display: option.display,
25
+ value: option.value
26
+ }
27
+ default:
28
+ return option
29
+ }
30
+ })
31
+
32
+ return operator === 'in'
33
+ ? html`
34
+ ${options
35
+ ?.filter((option: { display: string; value: any }) => !!option)
36
+ .map(
37
+ (option: { display: string; value: any }) =>
38
+ html` <ox-checkbox option value=${option.value}>${option.display}</ox-checkbox> `
39
+ )}
40
+ `
41
+ : html`
42
+ ${options?.map(
43
+ (option: { display: string; value: any }) =>
44
+ html` <div option value=${option.value}>${option.display}&nbsp;</div> `
45
+ )}
46
+ `
47
+ }
@@ -0,0 +1,87 @@
1
+ import { css } from 'lit'
2
+
3
+ export const FilterStyles = css`
4
+ :host {
5
+ --ox-input-placeholder-color: var(--primary-color);
6
+
7
+ --ox-input-padding: var(--padding-narrow) var(--padding-default);
8
+ --ox-input-border: 1px solid rgba(0, 0, 0, 0.2);
9
+ --ox-input-focus-border: 1px solid var(--primary-color);
10
+ --ox-input-border-radius: var(--border-radius);
11
+ --ox-input-font: normal 14px var(--theme-font);
12
+ --ox-input-color: var(--primary-text-color);
13
+
14
+ --ox-filters-form-gap: var(--margin-wide);
15
+ --ox-filters-form-label-font: var(--label-font);
16
+ --ox-filters-form-label-color: var(--label-color);
17
+ --ox-select-padding: 0 var(--padding-narrow);
18
+ }
19
+
20
+ label {
21
+ font: var(--ox-filters-form-label-font);
22
+ color: var(--ox-filters-form-label-color);
23
+ }
24
+ span {
25
+ margin-right: var(--margin-narrow);
26
+ text-transform: capitalize;
27
+ }
28
+
29
+ input::placeholder {
30
+ color: var(--ox-input-placeholder-color);
31
+ opacity: 0.6;
32
+ }
33
+
34
+ ox-select,
35
+ input {
36
+ padding: var(--ox-input-padding);
37
+ border: none;
38
+ border-bottom: var(--ox-input-border);
39
+ font: var(--ox-input-font);
40
+ color: var(--ox-input-color);
41
+ }
42
+
43
+ ox-select:focus,
44
+ input:focus {
45
+ outline: none;
46
+ border-bottom: var(--ox-input-focus-border);
47
+ color: var(--primary-color);
48
+ }
49
+ ox-select {
50
+ padding: var(--ox-select-padding);
51
+ }
52
+
53
+ input[type='number'] {
54
+ padding-right: var(--padding-narrow);
55
+ max-width: 90px;
56
+ }
57
+
58
+ input[type*='date'],
59
+ input[type*='time'],
60
+ input[type='week'],
61
+ input[type='month'] {
62
+ padding-right: var(--padding-narrow);
63
+ max-width: 170px;
64
+ }
65
+
66
+ @media only screen and (max-width: 460px) {
67
+ :host {
68
+ --ox-filters-form-label-font: bold 13px var(--theme-font);
69
+ --ox-input-font: normal 16px var(--theme-font);
70
+ }
71
+
72
+ ox-checkbox {
73
+ margin-top: var(--margin-narrow);
74
+ }
75
+
76
+ input[from],
77
+ input[to] {
78
+ max-width: 44%;
79
+ }
80
+
81
+ input[type*='datetime'][from],
82
+ input[type*='datetime'][to] {
83
+ padding-right: var(--padding-narrow);
84
+ min-width: 91%;
85
+ }
86
+ }
87
+ `
@@ -0,0 +1,9 @@
1
+ export * from './registry.js'
2
+
3
+ export * from './filter-select.js'
4
+ export * from './filter-input.js'
5
+ export * from './filter-checkbox.js'
6
+ export * from './filter-range-date.js'
7
+ export * from './filter-range-number.js'
8
+
9
+ export * from './ox-filters-form-base.js'
@@ -0,0 +1,261 @@
1
+ import '@operato/input/ox-checkbox.js'
2
+ import '@operato/input/ox-select.js'
3
+ import '@operato/popup/ox-popup-list.js'
4
+ import '@operato/input/ox-input-search.js'
5
+
6
+ import json5 from 'json5'
7
+ import { css, html, LitElement, PropertyValues, TemplateResult } from 'lit'
8
+ import { customElement, property, queryAsync } from 'lit/decorators.js'
9
+
10
+ import { FilterStyles } from './filter-styles.js'
11
+ import { getFilterRenderer } from './registry.js'
12
+ import { FilterConfig, FilterOperator, FilterValue } from './types.js'
13
+
14
+ export type QueryFilterRangeValue = [from: number, to: number]
15
+
16
+ export type QueryFilter = {
17
+ name: string
18
+ operator: FilterOperator
19
+ value: any
20
+ }
21
+
22
+ @customElement('ox-filters-form-base')
23
+ export class FiltersFormBase extends LitElement {
24
+ static styles = [
25
+ FilterStyles,
26
+ css`
27
+ :host {
28
+ display: flex;
29
+ }
30
+
31
+ form {
32
+ flex: 1;
33
+
34
+ display: flex;
35
+ flex-flow: row wrap;
36
+ gap: var(--ox-filters-form-gap);
37
+ }
38
+
39
+ form > * {
40
+ display: flex;
41
+ align-items: center;
42
+ }
43
+
44
+ @media only screen and (max-width: 460px) {
45
+ form {
46
+ flex-direction: column;
47
+ }
48
+
49
+ label,
50
+ label span,
51
+ ox-select {
52
+ display: block;
53
+ }
54
+ }
55
+ `
56
+ ]
57
+
58
+ @property({ type: Object }) value: FilterValue[] = []
59
+ @property({ type: Object }) filters: FilterConfig[] = []
60
+ @property({ type: Boolean, attribute: 'url-params-sensitive' }) urlParamsSensitive?: boolean
61
+
62
+ @queryAsync('form') form!: HTMLFormElement
63
+
64
+ private _popstateEventHandler?: EventListener
65
+ private _filtersChangeHandler?: EventListener
66
+
67
+ connectedCallback(): void {
68
+ super.connectedCallback()
69
+
70
+ this.renderRoot.addEventListener('change', async (e: Event) => {
71
+ this.dispatchEvent(
72
+ new CustomEvent('filters-change', {
73
+ bubbles: true,
74
+ composed: true,
75
+ detail: {
76
+ filters: await this.getQueryFilters(),
77
+ from: 'filters-form'
78
+ }
79
+ })
80
+ )
81
+ })
82
+ }
83
+
84
+ render(): TemplateResult {
85
+ return html`
86
+ <form
87
+ @submit=${(e: Event) => {
88
+ e.preventDefault()
89
+ }}
90
+ >
91
+ ${this.filters.map((filter: FilterConfig) => {
92
+ const { name, label, type, operator } = filter
93
+ const idx = operator === 'between' ? 1 : 0
94
+ const renderer = getFilterRenderer(
95
+ operator === 'like' || operator === 'i_like' || operator === 'i_nlike' || operator === 'nlike'
96
+ ? 'text'
97
+ : type
98
+ )[idx]
99
+ const value = this.value?.find(filter => filter.name == name)?.value
100
+
101
+ if (!renderer) {
102
+ return html``
103
+ }
104
+
105
+ return type === 'search'
106
+ ? html`<ox-input-search name="search" .value=${value}></ox-input-search>`
107
+ : type !== 'select'
108
+ ? html` <label filter-title><span>${label}</span> ${renderer(filter, value, this)} </label> `
109
+ : operator === 'in'
110
+ ? html`
111
+ <ox-select
112
+ name=${name}
113
+ placeholder=${label}
114
+ .value=${value}
115
+ @change=${(e: CustomEvent) =>
116
+ e.target?.dispatchEvent(
117
+ new CustomEvent('filter-change', {
118
+ detail: {
119
+ name,
120
+ operator,
121
+ value: e.detail
122
+ }
123
+ })
124
+ )}
125
+ >
126
+ <ox-popup-list multiple attr-selected="checked" with-search>
127
+ ${renderer(filter, value, this)}
128
+ </ox-popup-list>
129
+ </ox-select>
130
+ `
131
+ : html`
132
+ <ox-select
133
+ name=${name}
134
+ placeholder=${label}
135
+ .value=${value}
136
+ @change=${(e: CustomEvent) =>
137
+ e.target?.dispatchEvent(
138
+ new CustomEvent('filter-change', {
139
+ detail: {
140
+ name,
141
+ operator,
142
+ value: e.detail
143
+ }
144
+ })
145
+ )}
146
+ >
147
+ <ox-popup-list with-search> ${renderer(filter, value, this)} </ox-popup-list>
148
+ </ox-select>
149
+ `
150
+ })}
151
+ </form>
152
+ `
153
+ }
154
+
155
+ async updated(changes: PropertyValues<this>) {
156
+ if (changes.has('urlParamsSensitive')) {
157
+ if (this.urlParamsSensitive) {
158
+ this._popstateEventHandler = () => {
159
+ const queryString = window.location.search
160
+ const urlParams = new URLSearchParams(queryString)
161
+ var filters = urlParams.get('filters')
162
+
163
+ if (filters) {
164
+ try {
165
+ this.value = json5.parse(filters)
166
+ this.dispatchEvent(
167
+ new CustomEvent('filters-change', {
168
+ bubbles: true,
169
+ composed: true,
170
+ detail: {
171
+ filters: json5.parse(filters),
172
+ from: 'url-parameter'
173
+ }
174
+ })
175
+ )
176
+ } catch (e) {
177
+ console.error(`invalid filters params on URL query string : ${e}`)
178
+ }
179
+ }
180
+ }
181
+
182
+ //@ts-ignore
183
+ this._popstateEventHandler() // call for the first time
184
+
185
+ window.addEventListener('popstate', this._popstateEventHandler)
186
+
187
+ this._filtersChangeHandler = (e: Event) => {
188
+ const urlParams = new URLSearchParams(window.location.search)
189
+ const { filters, from } = (e as CustomEvent).detail || {}
190
+
191
+ if (from === 'url-parameter') {
192
+ return
193
+ }
194
+
195
+ if (filters.length > 0) {
196
+ urlParams.set('filters', json5.stringify(filters))
197
+ } else {
198
+ urlParams.delete('filters')
199
+ }
200
+ const queryString = String(urlParams)
201
+ const url = `${window.location.pathname}${queryString ? `?${queryString}` : ''}`
202
+
203
+ history.pushState({}, document.title, url)
204
+ }
205
+
206
+ this.addEventListener('filters-change', this._filtersChangeHandler)
207
+ } else {
208
+ if (this._popstateEventHandler) {
209
+ window.removeEventListener('popstate', this._popstateEventHandler)
210
+ }
211
+ if (this._filtersChangeHandler) {
212
+ this.removeEventListener('popstate', this._filtersChangeHandler)
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ async getQueryFilters(): Promise<QueryFilter[]> {
219
+ const formData = new FormData(await this.form)
220
+
221
+ var filters = this.filters
222
+ .map((filter: FilterConfig) => {
223
+ const { name, type, operator } = filter
224
+
225
+ var value = formData.getAll(name)
226
+ if (value.length == 0) {
227
+ return
228
+ }
229
+
230
+ if (-1 === value.findIndex(v => v !== '')) {
231
+ return
232
+ }
233
+
234
+ const filterValue = value.map(v => {
235
+ const value = v.toString()
236
+
237
+ /* TODO registry에서 타입별로 parsing 방법을 지정할 수 있도록 해야한다. */
238
+ switch (type) {
239
+ case 'integer':
240
+ case 'float':
241
+ case 'number':
242
+ case 'progress':
243
+ case 'checkbox':
244
+ case 'boolean':
245
+ return !value ? undefined : JSON.parse(value)
246
+ default:
247
+ return value
248
+ }
249
+ })
250
+
251
+ return {
252
+ name,
253
+ operator,
254
+ value: filterValue.length === 1 ? filterValue[0] : filterValue
255
+ }
256
+ })
257
+ .filter(result => result !== undefined) as QueryFilter[]
258
+
259
+ return filters
260
+ }
261
+ }
@@ -0,0 +1,51 @@
1
+ import { FilterCheckbox } from './filter-checkbox.js'
2
+ import { FilterInput } from './filter-input.js'
3
+ import { FilterRangeDate } from './filter-range-date.js'
4
+ import { FilterRangeNumber } from './filter-range-number.js'
5
+ import { FilterSelect } from './filter-select.js'
6
+ import { FilterSelectRenderer } from './types.js'
7
+
8
+ var RENDERERS: {
9
+ [name: string]: FilterSelectRenderer[]
10
+ } = {
11
+ string: [FilterInput],
12
+ text: [FilterInput],
13
+ email: [FilterInput],
14
+ tel: [FilterInput],
15
+ integer: [FilterInput, FilterRangeNumber],
16
+ float: [FilterInput, FilterRangeNumber],
17
+ number: [FilterInput, FilterRangeNumber],
18
+ select: [FilterSelect],
19
+ boolean: [FilterCheckbox],
20
+ checkbox: [FilterCheckbox],
21
+ month: [FilterInput, FilterRangeDate],
22
+ week: [FilterInput, FilterRangeDate],
23
+ date: [FilterInput, FilterRangeDate],
24
+ time: [FilterInput, FilterRangeDate],
25
+ datetime: [FilterInput, FilterRangeDate],
26
+ color: [FilterInput],
27
+ progress: [FilterInput, FilterRangeNumber],
28
+ link: [FilterInput],
29
+ image: [FilterInput],
30
+ json5: [FilterInput]
31
+ }
32
+
33
+ export function registerFilterRenderer(type: string, renderer: FilterSelectRenderer[]) {
34
+ RENDERERS[type] = renderer
35
+ }
36
+
37
+ export function unregisterFilterRenderer(type: string): void {
38
+ delete RENDERERS[type]
39
+ }
40
+
41
+ export function getFilterRenderers(): { [name: string]: FilterSelectRenderer[] } {
42
+ return { ...RENDERERS }
43
+ }
44
+
45
+ export function getFilterRenderer(type: string): FilterSelectRenderer[] {
46
+ if (typeof type == 'function') {
47
+ return type
48
+ }
49
+
50
+ return RENDERERS[type || 'text'] || [FilterInput]
51
+ }
@@ -0,0 +1,47 @@
1
+ import { TemplateResult } from 'lit-html'
2
+
3
+ export type FilterOperator =
4
+ | 'search'
5
+ | 'eq'
6
+ | 'between'
7
+ | 'gte'
8
+ | 'lte'
9
+ | 'is_not_true'
10
+ | 'in'
11
+ | 'like'
12
+ | 'i_like'
13
+ | 'noteq'
14
+ | 'is_empty_num_id'
15
+ | 'is_blank'
16
+ | 'is_present'
17
+ | 'is_not_false'
18
+ | 'is_true'
19
+ | 'is_false'
20
+ | 'is_not_null'
21
+ | 'is_null'
22
+ | 'notin_with_null'
23
+ | 'notin'
24
+ | 'gt'
25
+ | 'lt'
26
+ | 'i_nlike'
27
+ | 'nlike'
28
+
29
+ export type FilterConfig = {
30
+ name: string
31
+ label: string
32
+ type: string
33
+ operator?: FilterOperator
34
+ options?: { [key: string]: any }
35
+ }
36
+
37
+ export type FilterValue = {
38
+ name: string
39
+ operator: FilterOperator
40
+ value: string | number | boolean | string[] | number[] | undefined
41
+ }
42
+
43
+ export type FilterSelectRenderer = (
44
+ filter: FilterConfig,
45
+ value: string | number | boolean | string[] | number[] | any | undefined,
46
+ owner: Element
47
+ ) => TemplateResult | string | void
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from './styles'
2
2
  export * from './components/ox-search-form'
3
3
  export * from './components/ox-file-uploader'
4
+ export * from './filters'
@@ -0,0 +1,142 @@
1
+ body {
2
+ /* theme color */
3
+ --primary-color-rgb: 130, 105, 96;
4
+ --primary-color: rgb(var(--primary-color-rgb));
5
+ --secondary-color-rgb: 78, 78, 90;
6
+ --secondary-color: rgb(var(--secondary-color-rgb));
7
+ --focus-color: var(--theme-white-color);
8
+ --primary-background-color: var(--secondary-color);
9
+ --secondary-background-color: #283644;
10
+ --main-section-background-color: #f5f2ee;
11
+ --theme-white-color: #fff;
12
+ --theme-black-color: rgba(0, 0, 0, 0.9);
13
+
14
+ --focus-background-color: var(--primary-color);
15
+ --primary-text-color: #3c3938;
16
+ --secondary-text-color: var(--primary-color);
17
+
18
+ --opacity-dark-color: rgba(0, 0, 0, 0.4);
19
+ --opacity-light-color: rgba(255, 255, 255, 0.8);
20
+
21
+ /* status color */
22
+ --status-success-color: #35a24a;
23
+ --status-warning-color: #ee8d03;
24
+ --status-danger-color: #d14946;
25
+ --status-info-color: #398ace;
26
+
27
+ /* common style */
28
+ --border-radius: 4px;
29
+ --border-dark-color: 1px solid rgba(0, 0, 0, 0.15);
30
+ --border-light-color: 1px solid rgba(255, 255, 255, 0.3);
31
+
32
+ --box-shadow: 2px 2px 3px 0px rgba(0, 0, 0, 0.1);
33
+
34
+ --theme-font: 'Noto', Helvetica;
35
+
36
+ --margin-default: 9px;
37
+ --margin-narrow: 4px;
38
+ --margin-wide: 15px;
39
+ --padding-default: var(--margin-default);
40
+ --padding-narrow: var(--margin-narrow);
41
+ --padding-wide: var(--margin-wide);
42
+
43
+ --scrollbar-thumb-color: rgba(57, 78, 100, 0.5);
44
+ --scrollbar-thumb-hover-color: var(--primary-color);
45
+
46
+ --fontsize-default: 14px;
47
+ --fontsize-small: 13px;
48
+ --fontsize-large: 16px;
49
+
50
+ /* app layout style */
51
+ --app-grid-template-area: 'header header header' 'nav main aside' 'nav footer aside';
52
+
53
+ /* title & description style */
54
+ --title-margin: var(--margin-narrow) 0;
55
+ --title-font: bold 24px var(--theme-font);
56
+ --title-text-color: var(--secondary-color);
57
+ --title-font-mobile: bold 20px var(--theme-font);
58
+
59
+ --page-description-margin: var(--margin-narrow) 0 var(--margin-wide) 0;
60
+ --page-description-font: normal var(--fontsize-default) / 1.2rem var(--theme-font);
61
+ --page-description-color: var(--secondary-text-color);
62
+
63
+ --subtitle-padding: 12px 5px 3px 5px;
64
+ --subtitle-font: bold 18px var(--theme-font);
65
+ --subtitle-text-color: var(--primary-color);
66
+ --subtitle-border-bottom: 1px solid var(--primary-color);
67
+
68
+ /* icon style */
69
+ --icon-tiny-size: 24px;
70
+ --icon-default-size: 36px;
71
+ --icon-big-size: 48px;
72
+ --icon-default-color: var(--theme-white-color);
73
+
74
+ /* material design component themes */
75
+ --mdc-theme-on-primary: var(--theme-white-color);
76
+ --mdc-theme-primary: var(--secondary-text-color);
77
+ --mdc-theme-on-secondary: var(--theme-white-color);
78
+ --mdc-theme-secondary: var(--primary-color);
79
+ --mdc-button-outline-color: var(--primary-color);
80
+ --mdc-danger-button-primary-color: var(--status-danger-color);
81
+ --mdc-danger-button-outline-color: var(--status-danger-color);
82
+ --mdc-button-outline-width: 1px;
83
+ --mdc-button-horizontal-padding: 16px;
84
+
85
+ /* button style */
86
+ --button-background-color: #fafbfc;
87
+ --button-background-focus-color: var(--primary-color);
88
+ --button-border: var(--border-dark-color);
89
+ --button-border-radius: var(--border-radius);
90
+ --button-margin: var(--margin-default) var(--margin-default) var(--margin-default) 0;
91
+ --button-padding: var(--padding-default);
92
+ --button-color: var(--secondary-color);
93
+ --button-font: normal 15px var(--theme-font);
94
+ --button-text-transform: capitalize;
95
+ --button-active-box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.2);
96
+ --button-activ-border: 1px solid var(--primary-color);
97
+
98
+ --button-primary-background-color: var(--primary-color);
99
+ --button-primary-active-background-color: var(--status-success-color);
100
+ --button-primary-padding: var(--margin-default) var(--margin-wide);
101
+ --button-primary-color: var(--theme-white-color);
102
+ --button-primary-font: bold 16px var(--theme-font);
103
+
104
+ /* table style */
105
+ --th-padding: var(--padding-default) 0 var(--padding-default) var(--padding-default);
106
+ --th-border-top: 2px solid var(--secondary-color);
107
+ --th-text-transform: capitalize;
108
+ --th-font: bold var(--fontsize-small) var(--theme-font);
109
+ --th-color: rgba(var(--secondary-color-rgb), 0.8);
110
+
111
+ --tr-background-color: var(--theme-white-color);
112
+ --tr-background-odd-color: rgba(255, 255, 255, 0.4);
113
+ --tr-background-hover-color: #e1f5fe;
114
+ --td-border-bottom: 1px solid rgba(0, 0, 0, 0.09);
115
+ --td-padding: var(--padding-default);
116
+ --td-font: normal 13px var(--theme-font);
117
+ --td-color: var(--secondary-color);
118
+
119
+ /* form style */
120
+ --label-font: normal var(--fontsize-default) var(--theme-font);
121
+ --label-color: var(--secondary-color);
122
+ --label-text-transform: capitalize;
123
+ --input-margin: var(--margin-narrow) 0;
124
+ --input-padding: var(--padding-default);
125
+ --input-min-width: 200px;
126
+ --input-font: normal var(--fontsize-default) var(--theme-font);
127
+ --input-hint-font: normal var(--fontsize-small) var(--theme-font);
128
+ --input-hint-color: #666;
129
+ --input-container-max-width: 900px;
130
+ --fieldset-margin: var(--padding-wide) 0;
131
+ --fieldset-padding: 0 var(--padding-wide) var(--padding-wide) var(--padding-wide);
132
+ --legend-padding: var(--padding-default) 0;
133
+ --legend-color: var(--secondary-text-color);
134
+ --legend-font: bold 16px var(--theme-font);
135
+ }
136
+
137
+ @media only screen and (max-width: 460px) {
138
+ body {
139
+ /* subtitle style */
140
+ --subtitle-margin: 0;
141
+ }
142
+ }
File without changes