ywana-core8 0.0.1

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 (101) hide show
  1. package/dist/index.cjs +2 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.css +2 -0
  4. package/dist/index.css.map +1 -0
  5. package/dist/index.modern.js +2 -0
  6. package/dist/index.modern.js.map +1 -0
  7. package/dist/index.umd.js +2 -0
  8. package/dist/index.umd.js.map +1 -0
  9. package/package.json +27 -0
  10. package/publish.sh +5 -0
  11. package/src/css/fonts.css +162 -0
  12. package/src/css/html.css +36 -0
  13. package/src/css/theme.css +89 -0
  14. package/src/css/theme_dark.css +85 -0
  15. package/src/css/theme_light.css +87 -0
  16. package/src/domain/CollectionPage.css +34 -0
  17. package/src/domain/CollectionPage.js +346 -0
  18. package/src/domain/ContentEditor.css +174 -0
  19. package/src/domain/ContentEditor.js +425 -0
  20. package/src/domain/ContentForm.js +74 -0
  21. package/src/domain/ContentType.js +187 -0
  22. package/src/domain/CreateContentDialog.js +59 -0
  23. package/src/domain/EditContentDialog.js +50 -0
  24. package/src/domain/TablePage.css +29 -0
  25. package/src/domain/TablePage.js +395 -0
  26. package/src/domain/index.js +5 -0
  27. package/src/fonts/Assistant-Bold.ttf +0 -0
  28. package/src/fonts/Assistant-ExtraBold.ttf +0 -0
  29. package/src/fonts/Assistant-ExtraLight.ttf +0 -0
  30. package/src/fonts/Assistant-Light.ttf +0 -0
  31. package/src/fonts/Assistant-Medium.ttf +0 -0
  32. package/src/fonts/Assistant-Regular.ttf +0 -0
  33. package/src/fonts/Assistant-SemiBold.ttf +0 -0
  34. package/src/fonts/Assistant-VariableFont_wght.ttf +0 -0
  35. package/src/html/button.css +79 -0
  36. package/src/html/button.js +26 -0
  37. package/src/html/checkbox.css +51 -0
  38. package/src/html/checkbox.js +33 -0
  39. package/src/html/chip.css +63 -0
  40. package/src/html/chip.js +39 -0
  41. package/src/html/form.css +17 -0
  42. package/src/html/form.js +80 -0
  43. package/src/html/header.css +64 -0
  44. package/src/html/header.js +30 -0
  45. package/src/html/icon.css +53 -0
  46. package/src/html/icon.js +21 -0
  47. package/src/html/index.js +18 -0
  48. package/src/html/list.css +72 -0
  49. package/src/html/list.js +78 -0
  50. package/src/html/menu.css +76 -0
  51. package/src/html/menu.js +80 -0
  52. package/src/html/progress.css +20 -0
  53. package/src/html/progress.js +27 -0
  54. package/src/html/property.css +18 -0
  55. package/src/html/property.js +16 -0
  56. package/src/html/radio.css +50 -0
  57. package/src/html/radio.js +25 -0
  58. package/src/html/section.css +6 -0
  59. package/src/html/section.js +31 -0
  60. package/src/html/tab.css +45 -0
  61. package/src/html/tab.js +68 -0
  62. package/src/html/table.css +56 -0
  63. package/src/html/table.js +186 -0
  64. package/src/html/text.js +20 -0
  65. package/src/html/textfield-outlined.css +52 -0
  66. package/src/html/textfield.css +130 -0
  67. package/src/html/textfield.js +99 -0
  68. package/src/html/tokenfield.css +51 -0
  69. package/src/html/tokenfield.js +74 -0
  70. package/src/html/tree.css +63 -0
  71. package/src/html/tree.js +49 -0
  72. package/src/http/client.js +62 -0
  73. package/src/http/index.js +2 -0
  74. package/src/http/session.js +39 -0
  75. package/src/index.js +9 -0
  76. package/src/site/details.css +58 -0
  77. package/src/site/dialog.css +63 -0
  78. package/src/site/dialog.js +43 -0
  79. package/src/site/index.js +3 -0
  80. package/src/site/layouts.css +27 -0
  81. package/src/site/page.css +44 -0
  82. package/src/site/page.js +36 -0
  83. package/src/site/site.css +85 -0
  84. package/src/site/site.js +234 -0
  85. package/src/site/siteContext.js +4 -0
  86. package/src/site/workspace.js +57 -0
  87. package/src/upload/UploadArea.js +64 -0
  88. package/src/upload/UploadDialog.js +41 -0
  89. package/src/upload/UploadFile.js +31 -0
  90. package/src/upload/index.js +1 -0
  91. package/src/upload/uploader.css +57 -0
  92. package/src/upload/uploader.js +69 -0
  93. package/src/widgets/index.js +4 -0
  94. package/src/widgets/kanban/Kanban.css +80 -0
  95. package/src/widgets/kanban/Kanban.js +65 -0
  96. package/src/widgets/login/LoginBox.css +89 -0
  97. package/src/widgets/login/LoginBox.js +66 -0
  98. package/src/widgets/login/ResetPasswordBox.css +50 -0
  99. package/src/widgets/login/ResetPasswordBox.js +56 -0
  100. package/src/widgets/viewer/Viewer.css +87 -0
  101. package/src/widgets/viewer/Viewer.js +47 -0
@@ -0,0 +1,59 @@
1
+ import React, { Fragment, useContext, useMemo, useState } from 'react';
2
+ import { Button, Text } from '../html';
3
+ import { Dialog } from '../site/dialog'
4
+ import { SiteContext } from '../site/siteContext';
5
+ import { ContentForm } from './ContentForm';
6
+ import { Content } from './ContentType';
7
+
8
+ /**
9
+ * Create Content Dialog
10
+ */
11
+ export const CreateContentDialog = ({ label, type, validator, onOK, onError, onActionClose = true }) => {
12
+
13
+ const site = useContext(SiteContext);
14
+ const [form, setForm] = useState({})
15
+ const [isValid, setValid] = useState(false)
16
+ const [errors, setErrors] = useState([])
17
+
18
+ function change(form, validForm) {
19
+ setForm(form)
20
+ if (validator) {
21
+ const { validation, errors = [] } = validator(form)
22
+ setValid(validForm && validation)
23
+ setErrors(errors)
24
+ } else {
25
+ setValid(validForm)
26
+ }
27
+ }
28
+
29
+ function onAction(action) {
30
+ if (action === 'OK' && onOK) onOK(form)
31
+ if (action === 'CLOSE' || onActionClose === true) site.closeDialog();
32
+ }
33
+
34
+ function isRequired(field) {
35
+ const { required = false } = field
36
+ return required
37
+ }
38
+
39
+ function isOptional(field) {
40
+ const { creation = false } = field
41
+ return creation
42
+ }
43
+
44
+ const actions = (
45
+ <Fragment>
46
+ <Button label="CLOSE" action={() => onAction("CLOSE")}/>
47
+ <Button label="OK" action={() => onAction("OK")} disabled={!isValid} raised />
48
+ </Fragment>
49
+ )
50
+
51
+ const title = <Text use="headline6">{label}</Text>
52
+ const content = new Content(type, form)
53
+ return (
54
+ <Dialog title={title} open={true} onAction={onAction} actions={actions}>
55
+ <ContentForm content={content} filter={(field) => isRequired(field) || isOptional(field)} onChange={change} />
56
+ { errors.map ( error => <Text use="overline" tag="div" className="error">{error}</Text> )}
57
+ </Dialog>
58
+ )
59
+ }
@@ -0,0 +1,50 @@
1
+ import React, { Fragment, useState, useContext, useMemo } from 'react';
2
+ import { Dialog } from '../site'
3
+ import { SiteContext } from '../site/siteContext'
4
+ import { Text, Button } from '../html';
5
+ import { Content } from './ContentType'
6
+ import { ContentEditor } from './ContentEditor'
7
+
8
+ /**
9
+ * Edit Content Dialog
10
+ */
11
+ export const EditContentDialog = ({ label, type, value = {}, filter, validator, onOK, onError }) => {
12
+
13
+ const site = useContext(SiteContext);
14
+ const [form, setForm] = useState(value)
15
+ const [isValid, setValid] = useState(false)
16
+ const [errors, setErrors] = useState([])
17
+
18
+ function change(form) {
19
+ setForm(form)
20
+ if (validator) {
21
+ const { validation, errors = [] } = validator(form)
22
+ setValid(validation)
23
+ setErrors(errors)
24
+ } else {
25
+ setValid(true)
26
+ }
27
+ }
28
+
29
+ function onAction(action) {
30
+ if (action === 'OK' && onOK) onOK(Object.assign({}, value, form))
31
+ site.closeDialog();
32
+ }
33
+
34
+ const actions = (
35
+ <Fragment>
36
+ <Button label="CLOSE" action={() => onAction("CLOSE")} />
37
+ <div className="expand" />
38
+ <Button label="OK" action={() => onAction("OK")} disabled={!isValid} raised />
39
+ </Fragment>
40
+ )
41
+
42
+ const title = <Text use="headline6">{label}</Text>
43
+ const content = new Content(type, form)
44
+ return (
45
+ <Dialog title={title} open={true} onAction={onAction} actions={actions}>
46
+ <ContentEditor content={content} onChange={change} filter={filter} />
47
+ {errors.map(error => <Text use="overline" tag="div" className="error">{error}</Text>)}
48
+ </Dialog>
49
+ )
50
+ }
@@ -0,0 +1,29 @@
1
+ .table-page .datatable8 {
2
+ margin: 0 1rem;
3
+ }
4
+
5
+ menu.table-page>header {
6
+ padding-right: .5rem;
7
+ }
8
+
9
+ menu.table-page>main {
10
+ }
11
+
12
+ main.table-page {
13
+ display: flex;
14
+ flex-direction: column;
15
+ }
16
+
17
+ main.table-page>header {
18
+ padding-bottom: 1rem;
19
+ padding-right: 1rem;
20
+ }
21
+
22
+ aside.table-page {
23
+ width: 25rem;
24
+ }
25
+
26
+ main.table-editor {
27
+ flex: 1;
28
+ overflow: auto;
29
+ }
@@ -0,0 +1,395 @@
1
+ import equal from 'deep-equal'
2
+ import { Fragment, useContext, useEffect, useMemo, useRef, useState } from 'react'
3
+ import { EditContentDialog } from '.'
4
+ import { Content, ContentEditor, HTTPClient, PageContext, Session, SiteContext } from '..'
5
+ import { SCENARIO1 } from '../../dev/scenario1'
6
+ import { BuildGroupOptions } from '../../pages/kinds/options'
7
+ import { Button, DataTable, DropDown, Header, Icon, MenuIcon, MenuItem, Text } from '../html'
8
+ import "./TablePage.css"
9
+
10
+ const http = HTTPClient(window.API || process.env.REACT_APP_API, Session);
11
+
12
+ /**
13
+ * Table Page
14
+ */
15
+ export const TablePage = (props) => {
16
+
17
+ const site = useContext(SiteContext)
18
+ const { id = "table", icon, title, name = "table 1", schema, url, field, delay = 1000, actions, editable, canAdd = true, dev = true, autosave = true, groupBy, validator, scenario } = props
19
+ const [pageContext, setPageContext] = useContext(PageContext)
20
+ const { selected } = pageContext
21
+ const timer = useRef(null)
22
+ const [form, setForm] = useState(selected)
23
+
24
+ useEffect(() => {
25
+ setForm(selected)
26
+ }, [selected])
27
+
28
+ useEffect(() => {
29
+ if (autosave) {
30
+ clearTimeout(timer.current)
31
+ timer.current = setTimeout(() => {
32
+ if (canSave()) save()
33
+ }, delay)
34
+ }
35
+ }, [form])
36
+
37
+ useEffect(async () => {
38
+ const context = TableContext(url, field)
39
+ await context.load()
40
+ setPageContext(context)
41
+ }, [])
42
+
43
+ function add() {
44
+ const onOK = async (form) => {
45
+ await pageContext.create(form);
46
+ setPageContext(Object.assign({}, pageContext))
47
+ }
48
+ site.openDialog(<EditContentDialog label={`Crear ${name}`} type={schema} onOK={onOK} validator={validator} />);
49
+ }
50
+
51
+ function change(next) {
52
+ setForm(next)
53
+ }
54
+
55
+ function canSave() {
56
+ const can = !equal(form, selected)
57
+ return can
58
+ }
59
+
60
+ async function save() {
61
+ console.log('saving.....')
62
+ await pageContext.update(form)
63
+ setPageContext(Object.assign({}, pageContext))
64
+ }
65
+
66
+ async function playScenario() {
67
+ const promises1 = pageContext.all.map(async item => await pageContext.remove(item.id))
68
+ Promise.all(promises1).then(async () => {
69
+ const promises2 = SCENARIO1.map(async (item) => await pageContext.create(item))
70
+ Promise.all(promises2).then(async () => {
71
+ await pageContext.load()
72
+ setPageContext(Object.assign({}, pageContext))
73
+ })
74
+ })
75
+ }
76
+
77
+ function closeAside() {
78
+ pageContext.select(null)
79
+ setPageContext(Object.assign({}, pageContext))
80
+ }
81
+
82
+ function renderAside() {
83
+ if (selected && form) {
84
+ const content = new Content(schema, form)
85
+ return (
86
+ <aside className="table-page">
87
+ <Header icon="local_offer" title={selected.name || "Propiedades"}>
88
+ <Icon icon="close" clickable action={closeAside} />
89
+ </Header>
90
+ <ContentEditor content={content} onChange={change} />
91
+ </aside>
92
+ )
93
+ }
94
+ return null;
95
+ }
96
+
97
+ return (
98
+ <Fragment>
99
+ <Header className="table-page" title={<Text>{title}</Text>}>
100
+ {canAdd ? <Button icon="add" label="Añadir" action={add} raised /> : null}
101
+ {dev ? (
102
+ <MenuIcon align="alignRight">
103
+ <MenuItem label="Cargar Escenario 1" onSelect={playScenario} />
104
+ </MenuIcon>
105
+ ) : null}
106
+ {actions}
107
+ </Header>
108
+ <menu className="table-page">
109
+ <TableFilters schema={schema} />
110
+ </menu>
111
+ <main key={id} className="table-page">
112
+ <TableEditor icon={icon} title={name} schema={schema} delay={delay} editable={editable} groupBy={groupBy} />
113
+ </main>
114
+ {renderAside()}
115
+ </Fragment>
116
+ )
117
+ }
118
+
119
+ /**
120
+ * Table Filters
121
+ */
122
+ const TableFilters = (props) => {
123
+
124
+ const [pageContext, setPageContext] = useContext(PageContext)
125
+ const { schema } = props
126
+ const [form, setForm] = useState({})
127
+
128
+ const filterSchema = useMemo(() => {
129
+ const filterSchema = Object.assign({}, schema)
130
+ Object.values(filterSchema).forEach(field => field.section = null)
131
+ delete filterSchema.flows
132
+ return filterSchema
133
+ }, [schema])
134
+
135
+ async function change(next) {
136
+ setForm(next)
137
+ await pageContext.load(next)
138
+ setPageContext(Object.assign({}, pageContext))
139
+ }
140
+
141
+ function clear() {
142
+ setForm({})
143
+ }
144
+
145
+ const content = new Content(filterSchema, form)
146
+ return (
147
+ <Fragment>
148
+ <Header title={<Text>Filtros</Text>} >
149
+ <Button icon="filter_list_off" label="Limpiar" action={clear} />
150
+ </Header>
151
+ <main>
152
+ <ContentEditor content={content} onChange={change} />
153
+ </main>
154
+ </Fragment>
155
+ )
156
+ }
157
+
158
+ /**
159
+ * Table Editor
160
+ */
161
+ const TableEditor = (props) => {
162
+
163
+ const [pageContext, setPageContext] = useContext(PageContext)
164
+ const { all = [], filters } = pageContext
165
+ const { icon, title, schema, editable } = props
166
+ const [groupBy, setGroupBy] = useState(props.groupBy)
167
+
168
+ function changeGroup(id, value) {
169
+ setGroupBy(value)
170
+ }
171
+
172
+ async function remove(id) {
173
+ await pageContext.remove(id)
174
+ pageContext.clear()
175
+ setPageContext(Object.assign({}, pageContext))
176
+ }
177
+
178
+ function change(rowID, cellID, value) {
179
+ const row = all.find(r => r.id === rowID)
180
+ const next = Object.assign({}, row, { [cellID]: value })
181
+ delete next.actions
182
+ pageContext.update(next)
183
+ setPageContext(Object.assign({}, pageContext))
184
+ }
185
+
186
+ function clear() {
187
+ pageContext.clear()
188
+ setPageContext(Object.assign({}, pageContext))
189
+ }
190
+
191
+ async function select(row, event) {
192
+ clear()
193
+ await pageContext.select(row.id)
194
+ setPageContext(Object.assign({}, pageContext))
195
+ }
196
+
197
+ function renderGroups() {
198
+
199
+ const groups = all.reduce((groups, filter) => {
200
+ const groupName = filter[groupBy]
201
+ const group = groups[groupName]
202
+ if (!group) groups[groupName] = []
203
+ groups[groupName].push(filter)
204
+ return groups
205
+ }, {})
206
+
207
+
208
+ return Object.keys(groups).map(groupName => {
209
+
210
+ const table = {
211
+ columns: Object.values(schema)
212
+ .filter(field => field.column === true)
213
+ .map(field => {
214
+ let options = field.options;
215
+ if (options && typeof (options) == 'function') {
216
+ options = options()
217
+ }
218
+ return {
219
+ id: field.id,
220
+ label: field.label,
221
+ type: field.type,
222
+ onChange: field.editable ? change : null,
223
+ options
224
+ }
225
+ }),
226
+ rows: groups[groupName]
227
+ .map(item => {
228
+ item.actions = [<Icon icon="delete" size="small" clickable action={() => remove(item.id)} />]
229
+ return item
230
+ })
231
+ }
232
+
233
+ table.columns.push({ id: "actions" })
234
+
235
+ return (
236
+ <Fragment key={groupName}>
237
+ <Header title={groupName} />
238
+ <DataTable {...table} onRowSelection={select} editable={editable} />
239
+ </Fragment>
240
+ )
241
+ })
242
+ }
243
+
244
+ const table = {
245
+ columns: Object.values(schema)
246
+ .filter(field => field.column === true)
247
+ .map(field => {
248
+ let options = field.options;
249
+ if (options && typeof (options) == 'function') {
250
+ options = options()
251
+ }
252
+ return {
253
+ id: field.id,
254
+ label: field.label,
255
+ type: field.type,
256
+ onChange: field.editable ? change : null,
257
+ options
258
+ }
259
+ }),
260
+ rows: all
261
+ .map(item => {
262
+ item.actions = [<Icon icon="delete" size="small" clickable action={() => remove(item.id)} />]
263
+ return item
264
+ })
265
+ }
266
+
267
+ table.columns.push({ id: "actions" })
268
+
269
+ return (
270
+ <Fragment>
271
+ <Header icon={icon} title={<Text>{title}</Text>}>
272
+ <DropDown id="groupBy" label="Agrupar Por" value={groupBy} options={BuildGroupOptions(schema)} onChange={changeGroup} />
273
+ </Header>
274
+ <main className="table-editor">
275
+ {renderGroups()}
276
+ </main>
277
+ </Fragment>
278
+ )
279
+ }
280
+
281
+ /**
282
+ * table Context
283
+ */
284
+ const TableContext = (url, field) => {
285
+
286
+ const API = TableAPI(url)
287
+
288
+ return {
289
+
290
+ all: [],
291
+ selected: null,
292
+ filters: {},
293
+
294
+ async load(filter) {
295
+ try {
296
+
297
+ const filters = filter ? Object.keys(filter).reduce((filters, key) => {
298
+ const field = filter[key];
299
+ if (field) filters[key] = field;
300
+ return filters;
301
+ }, {}) : []
302
+
303
+ const data = await API.all(filters);
304
+ this.all = field ? data[field] : data;
305
+ } catch (error) {
306
+ console.log(error)
307
+ }
308
+ return
309
+ },
310
+
311
+ select(id) {
312
+ const result = this.all.find(item => item.id === id);
313
+ this.selected = result;
314
+ },
315
+
316
+ clear() {
317
+ this.selected = null
318
+ },
319
+
320
+ async create(form) {
321
+ try {
322
+ await API.create(form);
323
+ await this.load();
324
+ } catch (error) {
325
+ console.log(error)
326
+ }
327
+ return
328
+ },
329
+
330
+ async update(form) {
331
+ try {
332
+ await API.update(form)
333
+ await this.load()
334
+ } catch (error) {
335
+ console.log(error)
336
+ }
337
+ return
338
+ },
339
+
340
+ async remove(id) {
341
+ try {
342
+ await API.remove(id)
343
+ await this.load()
344
+ } catch (error) {
345
+ console.log(error)
346
+ }
347
+ return
348
+ },
349
+
350
+ changeFilters(filters) {
351
+ this.filters = filters
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * table API
358
+ */
359
+ const TableAPI = (url) => {
360
+
361
+ return {
362
+ all(filters) {
363
+ let queryParams = "?"
364
+ if (filters) {
365
+ const filterQuery = Object.keys(filters).reduce((query, key) => {
366
+ if (typeof (filters[key]) === 'boolean') {
367
+ return query.concat(`${key}=${filters[key]}&`)
368
+ } else {
369
+ return query.concat(`${key}=%${filters[key]}%&`)
370
+ }
371
+ }, "")
372
+ queryParams = queryParams.concat(filterQuery)
373
+ }
374
+ return http.GET(url + queryParams)
375
+ },
376
+
377
+ find(id) {
378
+ return http.GET(`${url}/${id}`)
379
+ },
380
+
381
+ create(form) {
382
+ const body = JSON.stringify(form)
383
+ return http.POST(url, body)
384
+ },
385
+
386
+ update(form) {
387
+ const body = JSON.stringify(form)
388
+ return http.PUT(`${url}/${form.id}`, body)
389
+ },
390
+
391
+ remove(id) {
392
+ return http.DELETE(`${url}/${id}`)
393
+ }
394
+ }
395
+ }
@@ -0,0 +1,5 @@
1
+ export { TYPES, Content } from './ContentType'
2
+ export { ContentForm } from './ContentForm'
3
+ export { ContentEditor, TabbedContentEditor, CollectionEditor, TreededContentEditor, FieldEditor, ListEditor } from './ContentEditor'
4
+ export { CreateContentDialog } from './CreateContentDialog'
5
+ export { EditContentDialog } from './EditContentDialog'
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,79 @@
1
+ .btn {
2
+ position: relative;
3
+ padding: .2rem .5rem;
4
+ display: flex;
5
+ align-items: center;
6
+ overflow: hidden;
7
+ border-width: 0;
8
+ outline: none;
9
+ transition: background-color .3s;
10
+ display: flex;
11
+ color: var(--primary-color);
12
+ min-height: 2.5rem;
13
+ }
14
+
15
+
16
+ .btn.outlined {
17
+ color: var(--primary-color);
18
+ border: solid 1px var(--primary-color);
19
+ }
20
+
21
+ .btn.outlined.disabled {
22
+ border: solid 1px rgba(200,200,200,.3) !important;
23
+ color: rgba(100,100,100,.5) !important;
24
+ cursor: not-allowed;
25
+ }
26
+
27
+ .btn.raised {
28
+ color: var(--primary-color-text);
29
+ background-color: var(--primary-color)
30
+ }
31
+
32
+ .btn.raised.disabled {
33
+ background-color: rgba(200,200,200,.3) !important;
34
+ color: rgba(100,100,100,.5) !important;
35
+ cursor: not-allowed;
36
+ }
37
+
38
+ .btn:hover, .btn:focus {
39
+ background-color: rgba(171, 171, 171, 0.3);
40
+ cursor: pointer;
41
+ }
42
+
43
+ .btn.raised:hover, .btn.raised:focus {
44
+ background-color: var(--primary-color-dark);
45
+ cursor: pointer;
46
+ }
47
+
48
+ .btn > * {
49
+ position: relative;
50
+ }
51
+
52
+ .btn span {
53
+ flex: 1;
54
+ display: block;
55
+ }
56
+
57
+ .btn:before {
58
+ content: "";
59
+ position: absolute;
60
+ top: 50%;
61
+ left: 50%;
62
+ display: block;
63
+ width: 0;
64
+ padding-top: 0;
65
+ border-radius: 100%;
66
+ background-color: rgba(236, 240, 241, .3);
67
+ -webkit-transform: translate(-50%, -50%);
68
+ -moz-transform: translate(-50%, -50%);
69
+ -ms-transform: translate(-50%, -50%);
70
+ -o-transform: translate(-50%, -50%);
71
+ transform: translate(-50%, -50%);
72
+ }
73
+
74
+ .btn:active:before {
75
+ width: 120%;
76
+ padding-top: 120%;
77
+ transition: width .2s ease-out, padding-top .2s ease-out;
78
+ }
79
+
@@ -0,0 +1,26 @@
1
+ import React from 'react'
2
+ import './button.css'
3
+ import { Icon } from './icon'
4
+
5
+ /**
6
+ * HTML Button
7
+ */
8
+ export const Button = ({ label, icon, action, disabled = false, outlined, raised }) => {
9
+
10
+ function click(event) {
11
+ if (!disabled) {
12
+ event.stopPropagation();
13
+ event.preventDefault();
14
+ if (action) action()
15
+ }
16
+ }
17
+
18
+ let style = raised ? 'raised' : outlined ? 'outlined' : 'normal'
19
+ if (disabled) style = `${style} disabled`
20
+ return (
21
+ <button className={`btn ${style}`} onClick={click}>
22
+ { icon ? <Icon icon={icon} size="small" /> : null }
23
+ <span>{ label }</span>
24
+ </button>
25
+ )
26
+ }