react-ui-basics 0.0.175 → 0.0.178

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 (162) hide show
  1. package/Animated.js.map +1 -1
  2. package/AutocompleteSelect.js.map +1 -1
  3. package/Button.js.map +1 -1
  4. package/CalendarMonthView.css +20 -0
  5. package/CalendarMonthView.d.ts +12 -0
  6. package/CalendarMonthView.js +52 -0
  7. package/CalendarMonthView.js.map +1 -0
  8. package/Checkbox.js.map +1 -1
  9. package/CircleProgress.js.map +1 -1
  10. package/CommonFields.js.map +1 -1
  11. package/DNDContainer.js.map +1 -1
  12. package/DNDDraggable.js.map +1 -1
  13. package/DNDDroppable.js.map +1 -1
  14. package/DatePicker.css +93 -0
  15. package/DatePicker.d.ts +32 -0
  16. package/DatePicker.js +116 -0
  17. package/DatePicker.js.map +1 -0
  18. package/DateTools.d.ts +39 -0
  19. package/DateTools.js +73 -0
  20. package/DateTools.js.map +1 -0
  21. package/Dialog.js.map +1 -1
  22. package/DropFileInput.js.map +1 -1
  23. package/DroppableHorizontalList.js.map +1 -1
  24. package/DroppableList.js.map +1 -1
  25. package/Dropzone.js.map +1 -1
  26. package/FilteredList.js.map +1 -1
  27. package/FloatingActionButton.js.map +1 -1
  28. package/FormUploadProgress.js.map +1 -1
  29. package/ImageGallery.js.map +1 -1
  30. package/MaterialIcon.js.map +1 -1
  31. package/Modal.js.map +1 -1
  32. package/ModalMenu.js.map +1 -1
  33. package/ReactConstants.js.map +1 -1
  34. package/ReactCreateElement.js.map +1 -1
  35. package/ReadyListener.js.map +1 -1
  36. package/Scrollable.js.map +1 -1
  37. package/ScrollableTable.js.map +1 -1
  38. package/Size.js.map +1 -1
  39. package/Snackbar.js.map +1 -1
  40. package/SpinningProgress.js.map +1 -1
  41. package/Switch.js.map +1 -1
  42. package/Table.js.map +1 -1
  43. package/Tabs.css +65 -0
  44. package/Tabs.d.ts +22 -0
  45. package/TextField.js.map +1 -1
  46. package/Tools.d.ts +2 -2
  47. package/Tools.js.map +1 -1
  48. package/bundle.css +726 -587
  49. package/index.js +4 -0
  50. package/index.js.map +1 -1
  51. package/package.json +1 -1
  52. package/router/HistoryTools.js.map +1 -1
  53. package/router/Link.js.map +1 -1
  54. package/router/NavLink.js.map +1 -1
  55. package/router/Route.js.map +1 -1
  56. package/router/index.js.map +1 -1
  57. package/src/Animated.css +7 -0
  58. package/src/Animated.js +219 -0
  59. package/src/Animated.scss +8 -0
  60. package/src/AutocompleteSelect.css +256 -0
  61. package/src/AutocompleteSelect.js +473 -0
  62. package/src/AutocompleteSelect.scss +322 -0
  63. package/src/Button.css +138 -0
  64. package/src/Button.js +104 -0
  65. package/src/Button.scss +177 -0
  66. package/src/CalendarMonthView.css +20 -0
  67. package/src/CalendarMonthView.scss +25 -0
  68. package/src/CalendarMonthView.tsx +80 -0
  69. package/src/Checkbox.css +56 -0
  70. package/src/Checkbox.js +52 -0
  71. package/src/Checkbox.scss +87 -0
  72. package/src/CircleProgress.css +11 -0
  73. package/src/CircleProgress.js +38 -0
  74. package/src/CircleProgress.scss +13 -0
  75. package/src/CommonFields.js +21 -0
  76. package/src/DNDContainer.css +26 -0
  77. package/src/DNDContainer.js +263 -0
  78. package/src/DNDContainer.scss +33 -0
  79. package/src/DNDDraggable.js +55 -0
  80. package/src/DNDDroppable.js +58 -0
  81. package/src/DatePicker.css +93 -0
  82. package/src/DatePicker.scss +118 -0
  83. package/src/DatePicker.tsx +179 -0
  84. package/src/DateTools.ts +85 -0
  85. package/src/Dialog.css +36 -0
  86. package/src/Dialog.js +160 -0
  87. package/src/Dialog.scss +42 -0
  88. package/src/DropFileInput.css +24 -0
  89. package/src/DropFileInput.js +45 -0
  90. package/src/DropFileInput.scss +28 -0
  91. package/src/DroppableHorizontalList.js +190 -0
  92. package/src/DroppableList.css +14 -0
  93. package/src/DroppableList.js +201 -0
  94. package/src/DroppableList.scss +27 -0
  95. package/src/Dropzone.css +54 -0
  96. package/src/Dropzone.js +140 -0
  97. package/src/Dropzone.scss +66 -0
  98. package/src/FilteredList.css +68 -0
  99. package/src/FilteredList.js +196 -0
  100. package/src/FilteredList.scss +86 -0
  101. package/src/FloatingActionButton.css +20 -0
  102. package/src/FloatingActionButton.js +35 -0
  103. package/src/FloatingActionButton.scss +25 -0
  104. package/src/FormUploadProgress.css +61 -0
  105. package/src/FormUploadProgress.js +87 -0
  106. package/src/FormUploadProgress.scss +78 -0
  107. package/src/ImageGallery.css +196 -0
  108. package/src/ImageGallery.js +935 -0
  109. package/src/ImageGallery.scss +256 -0
  110. package/src/MaterialIcon.js +1837 -0
  111. package/src/Modal.css +61 -0
  112. package/src/Modal.js +160 -0
  113. package/src/Modal.scss +73 -0
  114. package/src/ModalMenu.css +30 -0
  115. package/src/ModalMenu.js +59 -0
  116. package/src/ModalMenu.scss +36 -0
  117. package/src/ReactConstants.ts +42 -0
  118. package/src/ReactCreateElement.js +5 -0
  119. package/src/ReadyListener.js +53 -0
  120. package/src/ResizeObserver.ts +50 -0
  121. package/src/Scrollable.css +80 -0
  122. package/src/Scrollable.js +388 -0
  123. package/src/Scrollable.scss +100 -0
  124. package/src/ScrollableTable.css +6 -0
  125. package/src/ScrollableTable.scss +12 -0
  126. package/src/ScrollableTable.tsx +39 -0
  127. package/src/Size.js +56 -0
  128. package/src/Snackbar.css +40 -0
  129. package/src/Snackbar.js +71 -0
  130. package/src/Snackbar.scss +47 -0
  131. package/src/SpinningProgress.css +49 -0
  132. package/src/SpinningProgress.js +19 -0
  133. package/src/SpinningProgress.scss +57 -0
  134. package/src/Switch.css +69 -0
  135. package/src/Switch.js +54 -0
  136. package/src/Switch.scss +73 -0
  137. package/src/Table.css +94 -0
  138. package/src/Table.scss +121 -0
  139. package/src/Table.tsx +360 -0
  140. package/src/Tabs.css +65 -0
  141. package/src/Tabs.scss +77 -0
  142. package/src/Tabs.tsx +63 -0
  143. package/src/TextField.css +122 -0
  144. package/src/TextField.js +162 -0
  145. package/src/TextField.scss +160 -0
  146. package/src/Tools.test.js +105 -0
  147. package/src/Tools.ts +231 -0
  148. package/src/global.css +1 -0
  149. package/src/global.scss +6 -0
  150. package/src/index.js +84 -0
  151. package/src/router/HistoryTools.js +32 -0
  152. package/src/router/Link.js +27 -0
  153. package/src/router/NavLink.js +25 -0
  154. package/src/router/README.md +55 -0
  155. package/src/router/Route.js +209 -0
  156. package/src/router/Route.test.js +139 -0
  157. package/src/router/index.js +14 -0
  158. package/src/store/ProxyTools.js +91 -0
  159. package/src/store/ProxyTools.test.js +97 -0
  160. package/src/store/Store.ts +91 -0
  161. package/store/ProxyTools.js.map +1 -1
  162. package/store/Store.js.map +1 -1
package/src/Table.tsx ADDED
@@ -0,0 +1,360 @@
1
+ import React, {ChangeEvent, Component, ReactElement, SyntheticEvent} from 'react';
2
+ import './Table.css'
3
+ import TextField from "./TextField";
4
+ import AutocompleteSelect, {MODE_DEFAULT, MODE_MULTIPLE_MINI} from "./AutocompleteSelect";
5
+ import Switch from "./Switch";
6
+ import {Comparators, NOOP, classNames, preventDefault, stopPropagation, isFunction, isString, orNoop, createRef, setInterval, createAccessor, UNDEFINED} from "./Tools";
7
+ import MaterialIcon from "./MaterialIcon";
8
+ import {componentDidUpdate, componentWillUnmount, render, PureComponent, propsGetter, componentDidMount, stateGSs} from "./ReactConstants";
9
+
10
+ export const SORT_ASC = Comparators.SORT_ASC;
11
+ export const SORT_DESC = Comparators.SORT_DESC;
12
+
13
+ const getId = item => item.id;
14
+
15
+ type SortOrder = typeof SORT_ASC | typeof SORT_DESC;
16
+ type Comparator = <T>(a: T, b: T) => number;
17
+
18
+ const editorSelect = 'Select'
19
+ const editorSwitch = 'Switch'
20
+
21
+ export interface RowProps<T> {
22
+ i: number,
23
+ item: T,
24
+ disabled?: (t: T) => boolean,
25
+ editing,
26
+ onClick?: (t: T, e: SyntheticEvent) => void,
27
+ className?: string | ((e: T) => string),
28
+ columns: TableColumn<T>[],
29
+ cancelEditing: () => void,
30
+ onFinishEditing: () => void,
31
+ handleInputChange: (e: ChangeEvent) => void,
32
+ setValue: (v: any) => void,
33
+ setEditing: (t: T, i: number, value?: any) => void,
34
+ onMouseDown?: (e: SyntheticEvent, row: number, column: number) => void,
35
+ onMouseUp?: (e: SyntheticEvent, row: number, column: number) => void,
36
+ onMouseEnter?: (e: SyntheticEvent, row: number, column: number) => void,
37
+ onMouseLeave?: (e: SyntheticEvent, row: number, column: number) => void,
38
+ }
39
+
40
+ export type Formatter<T, V> =
41
+ ((value?: V, item?: T, format?: string) => ReactElement | string)
42
+ | ((value: V, item: T) => ReactElement | string)
43
+ | ((value: V) => ReactElement | string);
44
+
45
+ export type CellClassName<T> = (item: T, field: string, row: number, column: number) => string;
46
+ export type ColumnClassName<T> = string | CellClassName<T>
47
+
48
+ export interface TableColumn<T> {
49
+ field?: string,
50
+ className?: ColumnClassName<T>,
51
+ onClick?: (e: SyntheticEvent, startEditing: () => void, item: T, field: string, row: number, column: number) => void,
52
+ comparator?: Comparator,
53
+ header?: string | ReactElement,
54
+ sortable?: boolean,
55
+ displayEditor?: boolean,
56
+ formatter?: Formatter<T, any>,
57
+ format?: string,
58
+ editable?: boolean | ((t: T) => boolean),
59
+ editor?: typeof editorSelect | typeof editorSwitch | ((t: T, cancel: () => void, isEditing: boolean) => ReactElement),
60
+ preEditor?: (a: any) => any,
61
+ }
62
+
63
+ export interface TableProps<T> {
64
+ columns: TableColumn<T>[],
65
+ data: T[],
66
+ className?: string,
67
+ sortOrder?: SortOrder
68
+ sortBy?: string,
69
+ onSortChange?: (sortBy: string, sortOrder: SortOrder) => void,
70
+ onRowClick?: (t: T, e: SyntheticEvent) => void,
71
+ disabled?: (t: T) => boolean,
72
+ toKey?: (t: T) => number | string,
73
+ onChange?: (t: T, cancel: () => void) => void,
74
+ rowClassName?: string | ((t: T) => string),
75
+ onMouseDown?: (e: SyntheticEvent, row: number, column: number) => void,
76
+ onMouseUp?: (e: SyntheticEvent, row: number, column: number) => void,
77
+ onMouseEnter?: (e: SyntheticEvent, row: number, column: number) => void,
78
+ onMouseLeave?: (e: SyntheticEvent, row: number, column: number) => void,
79
+ }
80
+
81
+
82
+ export interface TableState<T> {
83
+ sortOrder?: SortOrder
84
+ sortBy?: string,
85
+ comparator?: Comparator,
86
+ data?: T[],
87
+ }
88
+
89
+ class Table<T> extends Component<TableProps<T>, TableState<T>> {
90
+ startEditing: (item, columnIndex, value?) => void;
91
+ finishEditing: () => void;
92
+ isEditing: () => boolean;
93
+
94
+ constructor(properties) {
95
+ super(properties);
96
+ const props = propsGetter(this);
97
+
98
+ const headers = createRef();
99
+ let updateHeadersInterval = 0;
100
+
101
+ const updateHeaders = () => {
102
+ const hidden = headers().querySelectorAll('.hidden');
103
+ const fixed = headers().querySelectorAll('.fixed');
104
+ for (let i = 0; i < hidden.length; i++) {
105
+ fixed[i].style.width = hidden[i].offsetWidth + 'px'
106
+ }
107
+ };
108
+
109
+ const [editingState, dataState, sortByState, sortOrderState, comparatorState] = stateGSs(this, 5);
110
+ const comparatorGetter = createAccessor('comparator');
111
+
112
+ const cancelEditing = () => {
113
+ editingState(false)
114
+ };
115
+
116
+ const setEditing = (item, columnIndex, value) => {
117
+ const {columns} = props()
118
+ const field = columns[columnIndex].field
119
+ editingState({id: item.id, value: (value != null ? value : item[field]), columnIndex, item, field})
120
+ };
121
+ this.startEditing = setEditing
122
+ this.isEditing = () => !!editingState()
123
+
124
+ const handleInputChange = (event) => {
125
+ const target = event.target;
126
+ const value = target.type === 'checkbox' ? target.checked : target.value;
127
+ editingState({...editingState(), value});
128
+ };
129
+
130
+ const onFinishEditing = () => {
131
+ const editing = editingState();
132
+ if (!editing || editing.value === null)
133
+ return;
134
+
135
+ let value = editing.value;
136
+ if (isString(value))
137
+ value = value.trim();
138
+
139
+ if (value === editing.item[editing.field])
140
+ return cancelEditing();
141
+
142
+ let updated = {
143
+ ...editing.item,
144
+ [editing.field]: value
145
+ };
146
+ props().onChange(updated, cancelEditing)
147
+ };
148
+ this.finishEditing = onFinishEditing
149
+
150
+ const setValue = (value) => {
151
+ editingState({...editingState(), value}, onFinishEditing)
152
+ };
153
+
154
+
155
+ const setData = (data, sortBy?, sortOrder?, comparator?) => {
156
+ data = data ? [...data] : [];
157
+ sortBy = sortBy || sortByState()
158
+ sortOrder = sortOrder || sortOrderState()
159
+ comparator = comparator || comparatorState()
160
+ if (sortBy) {
161
+ let c = comparator
162
+ if (c && sortOrder === SORT_DESC)
163
+ c = Comparators.inverse(c)
164
+
165
+ data.sort(c || Comparators.of(sortBy, sortOrder, data));
166
+ }
167
+ sortOrderState(sortOrder)
168
+ comparatorState(comparator)
169
+ sortByState(sortBy)
170
+ dataState(data, () => {
171
+ orNoop(props().onSortChange)(sortBy, sortOrder)
172
+ })
173
+ };
174
+
175
+ const sort = (e, sortBy, comparator) => {
176
+ preventDefault(e);
177
+ const sortOrder = sortByState() === sortBy && sortOrderState() === SORT_ASC ? SORT_DESC : SORT_ASC;
178
+ setData(dataState(), sortBy, sortOrder, comparator)
179
+ }
180
+
181
+ this[componentDidMount] = () => {
182
+ const {sortBy, sortOrder = SORT_ASC, columns, data} = props();
183
+ setData(data, sortBy, sortOrder, comparatorGetter(columns.find(it => it.field === sortBy)))
184
+ updateHeaders();
185
+ updateHeadersInterval = setInterval(updateHeaders, 1000)
186
+ };
187
+
188
+ this[componentDidUpdate] = (prevProps, prevState, snapshot) => {
189
+ const {sortBy, sortOrder, columns, data} = props();
190
+ if (sortBy !== prevProps.sortBy || sortOrder !== prevProps.sortOrder) {
191
+ setData(dataState(), sortBy, sortOrder || SORT_ASC, comparatorGetter(columns.find(it => it.field === sortBy)))
192
+ } else if (data !== prevProps.data) {
193
+ setData(data)
194
+ }
195
+ updateHeaders();
196
+ };
197
+
198
+ this[render] = () => {
199
+ const {columns = [], onRowClick = NOOP, disabled, className, rowClassName, toKey = getId, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave} = props();
200
+ const sortBy = sortByState()
201
+ const sortOrder = sortOrderState()
202
+ const editing = editingState();
203
+ const data = dataState() || [];
204
+ return (
205
+ <table className={classNames(`Table`, className)}>
206
+ <thead>
207
+ <tr ref={headers}>
208
+ {columns.filter(it => !!it).map((column, i) => {
209
+ const sortable = column.sortable || column.sortable == null;
210
+ return (
211
+ <th key={i} onClick={e => sortable && sort(e, column.field, comparatorGetter(column))}>
212
+ <div className={classNames('hidden', sortable && 'sortable', sortBy === column.field && 'active')}>
213
+ {column.header}
214
+ {column.header && sortBy === column.field && sortOrder === SORT_ASC && (<MaterialIcon icon={'keyboard_arrow_up'}/>)}
215
+ {column.header && sortBy === column.field && sortOrder === SORT_DESC && (<MaterialIcon icon={'keyboard_arrow_down'}/>)}
216
+ </div>
217
+ <div className={classNames('fixed', sortable && 'sortable', sortBy === column.field && 'active')}>
218
+ {column.header}
219
+ {column.header && sortBy === column.field && sortOrder === SORT_ASC && (<MaterialIcon icon={'keyboard_arrow_up'}/>)}
220
+ {column.header && sortBy === column.field && sortOrder === SORT_DESC && (<MaterialIcon icon={'keyboard_arrow_down'}/>)}
221
+ </div>
222
+ </th>
223
+ );
224
+ })}
225
+ </tr>
226
+ </thead>
227
+ <tbody>
228
+ {data.map((item, i) => (<Row
229
+ key={toKey(item)}
230
+ i={i}
231
+ item={item}
232
+ disabled={disabled}
233
+ editing={editing && editing.id === toKey(item) ? editing : null}
234
+ onClick={onRowClick}
235
+ className={rowClassName}
236
+ columns={columns}
237
+ setEditing={setEditing}
238
+ cancelEditing={cancelEditing}
239
+ onFinishEditing={onFinishEditing}
240
+ handleInputChange={handleInputChange}
241
+ setValue={setValue}
242
+ onMouseDown={onMouseDown}
243
+ onMouseUp={onMouseUp}
244
+ onMouseEnter={onMouseEnter}
245
+ onMouseLeave={onMouseLeave}
246
+ />
247
+ ))}
248
+ </tbody>
249
+ </table>
250
+ );
251
+ };
252
+
253
+ this[componentWillUnmount] = () => {
254
+ clearInterval(updateHeadersInterval)
255
+ };
256
+ }
257
+ }
258
+
259
+ const formatValue: (<T>(item: T, column: TableColumn<T>) => string | ReactElement) = (item, column) => {
260
+ const value = item[column.field];
261
+ const formatter = column.formatter;
262
+ return formatter ? (formatter as ((value?: any, item?: any, format?: string) => ReactElement))(value, item, column.format) : '' + value;
263
+ };
264
+
265
+ export interface TableColumnSelect extends TableColumn<any> {
266
+ editorData?: any,
267
+ multiSelect?: any,
268
+ onSelect?: any,
269
+ editorSelectedComponent?: any,
270
+ editorChildCompononent?: any,
271
+ prefilter?: any,
272
+ }
273
+
274
+ class Row<T> extends PureComponent<RowProps<T>> {
275
+ render() {
276
+ let {item, i, disabled, editing, onClick, className, columns, setEditing, cancelEditing, onFinishEditing, handleInputChange, setValue, onMouseDown, onMouseEnter, onMouseUp, onMouseLeave} = this.props;
277
+ return <tr onClick={e => onClick(item, e)}
278
+ className={classNames(
279
+ disabled && disabled(item) && 'disabled',
280
+ className && (isFunction(className) ? (className as (t: T) => string)(item) : className as string))
281
+ }>
282
+ {columns.filter(it => !!it).map((column, j) => {
283
+ const isEditing = editing && editing.columnIndex === j;
284
+ const editable = isFunction(column.editable) ? (column.editable as ((t: T) => boolean))(item) : !!column.editable;
285
+ const startEditing = !isEditing && editable ? () => setEditing(item, j) : NOOP;
286
+ const onClick = (!!column.onClick ? (e) => column.onClick(e, startEditing, item, column.field, i, j) : startEditing)
287
+
288
+ let displayEditor = isEditing || column.displayEditor;
289
+ let value = item[column.field];
290
+ if(editing) {
291
+ value = editing.value != null ? editing.value : value;
292
+ value = column.preEditor ? column.preEditor(value) : value
293
+ }
294
+ return (
295
+ <td key={j}
296
+ onMouseDown={onMouseDown ? (e) => onMouseDown(e, i, j) : UNDEFINED}
297
+ onMouseUp={onMouseUp ? (e) => onMouseUp(e, i, j) : UNDEFINED}
298
+ onMouseEnter={onMouseEnter ? (e) => onMouseEnter(e, i, j) : UNDEFINED}
299
+ onMouseLeave={onMouseLeave ? (e) => onMouseLeave(e, i, j) : UNDEFINED}
300
+ className={classNames(
301
+ editable ? 'editable' : 'ro',
302
+ isEditing && 'editing',
303
+ isString(column.className) && (column.className as string),
304
+ isFunction(column.className) && (column.className as CellClassName<T>)(item, column.field, i, j),
305
+ )}
306
+ onClick={onClick}>
307
+ {displayEditor && !column.editor && (
308
+ <TextField
309
+ value={value}
310
+ focused={true}
311
+ onBlur={cancelEditing}
312
+ onChange={handleInputChange}
313
+ onKeyDown={e => {
314
+ if (e.keyCode === 27/*escape*/) {
315
+ preventDefault(e);
316
+ cancelEditing();
317
+ }
318
+ if (e.keyCode === 13/*enter*/) {
319
+ preventDefault(e);
320
+ onFinishEditing();
321
+ }
322
+ }}
323
+ />
324
+ )}
325
+ {displayEditor && column.editor === editorSelect && (
326
+ <AutocompleteSelect
327
+ value={value}
328
+ data={(column as TableColumnSelect).editorData}
329
+ focused={true}
330
+ withArrow={false}
331
+ withFilter={false}
332
+ selectedMode={'inline'}
333
+ selectedComponent={(column as TableColumnSelect).editorSelectedComponent}
334
+ mode={(column as TableColumnSelect).multiSelect ? MODE_MULTIPLE_MINI : MODE_DEFAULT}
335
+ onChange={(column as TableColumnSelect).multiSelect && ((column as TableColumnSelect).onSelect ? (value => setValue((column as TableColumnSelect).onSelect(value))) : setValue)}
336
+ onSelect={!(column as TableColumnSelect).multiSelect && ((column as TableColumnSelect).onSelect ? (value => setValue((column as TableColumnSelect).onSelect(value))) : setValue)}
337
+ childComponent={(column as TableColumnSelect).editorChildCompononent}
338
+ onCancel={cancelEditing}
339
+ prefilter={(column as TableColumnSelect).prefilter}
340
+ />
341
+ )}
342
+ {displayEditor && column.editor === editorSwitch && (
343
+ <Switch onClick={e => {
344
+ stopPropagation(e);
345
+ preventDefault(e);
346
+ setValue(!value)
347
+ }} value={value}/>
348
+ )}
349
+ {displayEditor && isFunction(column.editor)
350
+ && (column.editor as ((t: T, cancel: () => void, isEditing: boolean) => ReactElement))(item, cancelEditing, isEditing)}
351
+ {!isEditing && !column.displayEditor && formatValue(item, column)}
352
+ </td>
353
+ );
354
+ })}
355
+ </tr>
356
+ }
357
+ }
358
+
359
+
360
+ export default Table;
package/src/Tabs.css ADDED
@@ -0,0 +1,65 @@
1
+ .Tabs {
2
+ display: block;
3
+ padding: 16px;
4
+ background: #fff;
5
+ }
6
+ .Tabs .visible {
7
+ position: relative;
8
+ opacity: 1;
9
+ height: auto;
10
+ transition: opacity 0.5s ease-out 0.25s;
11
+ }
12
+ .Tabs .hidden {
13
+ left: 0;
14
+ top: 0;
15
+ position: absolute;
16
+ opacity: 0;
17
+ transition: opacity 0.25s ease-out 0s, transform 1ms linear 0.25s;
18
+ transform: scale(0);
19
+ }
20
+ .Tabs .contents {
21
+ position: relative;
22
+ }
23
+ .Tabs .selector {
24
+ padding: 0;
25
+ margin: 0;
26
+ margin-bottom: 10px;
27
+ display: flex;
28
+ flex-flow: row nowrap;
29
+ position: relative;
30
+ }
31
+ .Tabs .selector .tab {
32
+ display: block;
33
+ height: 32px;
34
+ line-height: 32px;
35
+ padding: 0px 16px;
36
+ text-align: center;
37
+ background: #fff;
38
+ font-weight: bold;
39
+ text-decoration: none;
40
+ color: black;
41
+ font-size: 14px;
42
+ white-space: nowrap;
43
+ }
44
+ .Tabs .selector .tab.active {
45
+ color: #2196F3;
46
+ }
47
+ .Tabs .selector a:first-child {
48
+ border-top-left-radius: 2px;
49
+ }
50
+ .Tabs .selector a:last-of-type {
51
+ border-top-right-radius: 2px;
52
+ }
53
+ .Tabs .highlighter {
54
+ position: absolute;
55
+ z-index: 10;
56
+ bottom: 0;
57
+ height: 2px;
58
+ background: #2196F3;
59
+ width: 1px;
60
+ transform: translateX(0);
61
+ transform-origin: top left;
62
+ display: block;
63
+ left: 0;
64
+ transition: transform 0.23s ease-out;
65
+ }
package/src/Tabs.scss ADDED
@@ -0,0 +1,77 @@
1
+ @import "./global.scss";
2
+
3
+ .Tabs {
4
+ $main-size: 16px;
5
+
6
+ display: block;
7
+ padding: $main-size;
8
+ background: #fff;
9
+
10
+ .visible {
11
+ position: relative;
12
+ opacity: 1;
13
+ height: auto;
14
+ transition: opacity .5s ease-out 0.25s;
15
+ }
16
+ .hidden {
17
+ left: 0;
18
+ top: 0;
19
+ position: absolute;
20
+ opacity: 0;
21
+ transition: opacity .25s ease-out 0s, transform 1ms linear 0.25s;
22
+ transform: scale(0);
23
+ }
24
+
25
+ .contents {
26
+ position: relative;
27
+ }
28
+
29
+ .selector {
30
+ padding: 0;
31
+ margin: 0;
32
+ margin-bottom: 10px;
33
+ display: flex;
34
+ flex-flow: row nowrap;
35
+ position: relative;
36
+
37
+ .tab {
38
+ display: block;
39
+ height: $main-size*2;
40
+ line-height: $main-size*2;
41
+ padding: 0px 16px;
42
+ text-align: center;
43
+ background: #fff;
44
+ font-weight: bold;
45
+ text-decoration: none;
46
+ color: black;
47
+ font-size: 14px;
48
+ white-space: nowrap;
49
+
50
+ &.active {
51
+ color: $colorBlue;
52
+ }
53
+ }
54
+
55
+ a:first-child {
56
+ border-top-left-radius: 2px;
57
+ }
58
+ a:last-of-type {
59
+ border-top-right-radius: 2px;
60
+ }
61
+ }
62
+
63
+ .highlighter {
64
+ position: absolute;
65
+ z-index: 10;
66
+ bottom: 0;
67
+ height: 2px;
68
+ background: $colorBlue;
69
+ width: 1px;
70
+ transform: translateX(0);
71
+ transform-origin: top left;
72
+ display: block;
73
+ left: 0;
74
+ transition: transform .23s ease-out;
75
+ }
76
+
77
+ }
package/src/Tabs.tsx ADDED
@@ -0,0 +1,63 @@
1
+ import React, {PureComponent} from "react";
2
+ import "./Tabs.css";
3
+ import {classNames} from "./Tools";
4
+
5
+ export interface Tab {
6
+ label: string | JSX.Element
7
+ className?: string
8
+ renderer: (active: boolean) => JSX.Element
9
+ }
10
+
11
+ export interface TabsProps {
12
+ tabs: Tab[]
13
+ }
14
+
15
+ interface TabsState {
16
+ active: number,
17
+ highlighterPosition: number,
18
+ highlighterWidth: number,
19
+ }
20
+
21
+ class Tabs extends PureComponent<TabsProps, TabsState> {
22
+ private selector: HTMLDivElement;
23
+
24
+ render() {
25
+ const {tabs} = this.props;
26
+ const {active = 0, highlighterPosition = 0, highlighterWidth = 0} = this.state || {};
27
+ return (<div className="Tabs">
28
+ <div className="selector" ref={el => this.selector = el}>
29
+ {tabs.map((tab, i) => <a key={i}
30
+ className={classNames(`tab `, i === active && 'active', tab.className)}
31
+ href="#"
32
+ onClick={this.openTab}>
33
+ {tab.label}
34
+ </a>)}
35
+ <span className="highlighter" style={{transform: `translateX(${highlighterPosition}px) scale(${highlighterWidth},1)`}}/>
36
+ </div>
37
+ <div className="contents">
38
+ {tabs.map((tab, i) => <div key={i} className={`content ${i === active ? 'visible' : 'hidden'}`}>{tab.renderer(i === active)}</div>)}
39
+ </div>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ openTab = (e) => {
45
+ e.preventDefault();
46
+ let i = 0;
47
+ let child = e.target;
48
+ while ((child = child.previousSibling) != null) i++;
49
+
50
+ this.setState({active: i, highlighterPosition: e.target.offsetLeft, highlighterWidth: e.target.offsetWidth});
51
+ };
52
+
53
+ componentDidMount() {
54
+ const selected = this.selector.childNodes[0] as HTMLElement;
55
+ this.setState({active: 0, highlighterPosition: selected.offsetLeft, highlighterWidth: selected.offsetWidth});
56
+ }
57
+ }
58
+
59
+ export default Tabs;
60
+
61
+
62
+
63
+
@@ -0,0 +1,122 @@
1
+ .TextField {
2
+ position: relative;
3
+ width: 100%;
4
+ }
5
+ .TextField > label {
6
+ font-family: Roboto, sans-serif;
7
+ color: rgba(0, 0, 0, 0.3);
8
+ font-size: 16px;
9
+ position: absolute;
10
+ left: 0px;
11
+ top: 16px;
12
+ margin-top: -16px;
13
+ transition: all 0.2s ease;
14
+ user-select: none;
15
+ cursor: text;
16
+ }
17
+ .TextField > input {
18
+ font-family: Roboto, sans-serif;
19
+ color: #1e252d;
20
+ height: 19px;
21
+ font-size: 16px;
22
+ width: 100%;
23
+ border: 0px;
24
+ padding-bottom: 4px;
25
+ padding-top: 0;
26
+ outline-color: rgba(0, 0, 0, 0);
27
+ }
28
+ .TextField > input:focus {
29
+ outline: none;
30
+ }
31
+ .TextField > input:disabled {
32
+ color: gray;
33
+ background-color: white;
34
+ }
35
+ .TextField > input:disabled + .border {
36
+ height: 0px;
37
+ border-bottom: 2px dotted #e0e0e0;
38
+ }
39
+ .TextField > input::placeholder {
40
+ font-family: Roboto, sans-serif;
41
+ color: rgba(0, 0, 0, 0.3);
42
+ opacity: 1;
43
+ }
44
+ .TextField > .border {
45
+ border-bottom: 1px solid #e0e0e0;
46
+ height: 1px;
47
+ position: relative;
48
+ }
49
+ .TextField > .border .line {
50
+ left: 50%;
51
+ width: 0;
52
+ height: 2px;
53
+ min-height: 2px;
54
+ background-color: #00bcd4;
55
+ transition: all 0.2s ease;
56
+ position: absolute;
57
+ }
58
+ .TextField > .error {
59
+ text-align: left;
60
+ font-size: 10px;
61
+ min-height: 0;
62
+ line-height: 14px;
63
+ color: #f44336;
64
+ visibility: hidden;
65
+ opacity: 0;
66
+ transition: all 0.2s ease;
67
+ }
68
+ .TextField.focused > label, .TextField.withValue > label {
69
+ top: 4px;
70
+ font-size: 12px;
71
+ line-height: 14px;
72
+ }
73
+ .TextField.errored > .error {
74
+ visibility: visible;
75
+ opacity: 1;
76
+ }
77
+ .TextField.errored > .border {
78
+ border-bottom-color: #f44336;
79
+ }
80
+ .TextField.errored > label {
81
+ color: #f44336;
82
+ }
83
+ .TextField.focused > label {
84
+ color: #00bcd4;
85
+ }
86
+ .TextField.focused > .border .line {
87
+ left: 0;
88
+ width: 100%;
89
+ }
90
+ .TextField.disabled label, .TextField.disabled input {
91
+ cursor: not-allowed;
92
+ }
93
+ .TextField.withLabel {
94
+ margin-top: 15px;
95
+ }
96
+ .TextField input::-webkit-contacts-auto-fill-button {
97
+ visibility: hidden;
98
+ display: none !important;
99
+ pointer-events: none;
100
+ position: absolute;
101
+ right: 0;
102
+ }
103
+ .TextField input:-webkit-autofill {
104
+ -webkit-box-shadow: inset 0 0 0px 9999px white;
105
+ }
106
+ @keyframes shake {
107
+ 0%, 100% {
108
+ transform: translateX(0);
109
+ }
110
+ 20%, 60% {
111
+ transform: translateX(-5px);
112
+ }
113
+ 40%, 80% {
114
+ transform: translateX(5px);
115
+ }
116
+ }
117
+ .TextField .shake {
118
+ animation-duration: 0.5s;
119
+ animation-fill-mode: both;
120
+ animation-timing-function: ease-in-out;
121
+ animation-name: shake;
122
+ }