@furystack/shades-common-components 3.6.0 → 4.0.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.
Files changed (165) hide show
  1. package/dist/components/app-bar-link.js +20 -24
  2. package/dist/components/app-bar-link.js.map +1 -1
  3. package/dist/components/app-bar.js +9 -12
  4. package/dist/components/app-bar.js.map +1 -1
  5. package/dist/components/avatar.js +20 -18
  6. package/dist/components/avatar.js.map +1 -1
  7. package/dist/components/button.js +51 -37
  8. package/dist/components/button.js.map +1 -1
  9. package/dist/components/command-palette/command-palette-input.js +24 -29
  10. package/dist/components/command-palette/command-palette-input.js.map +1 -1
  11. package/dist/components/command-palette/command-palette-suggestion-list.js +38 -47
  12. package/dist/components/command-palette/command-palette-suggestion-list.js.map +1 -1
  13. package/dist/components/command-palette/index.js +34 -40
  14. package/dist/components/command-palette/index.js.map +1 -1
  15. package/dist/components/data-grid/body.js +6 -13
  16. package/dist/components/data-grid/body.js.map +1 -1
  17. package/dist/components/data-grid/data-grid-row.js +29 -31
  18. package/dist/components/data-grid/data-grid-row.js.map +1 -1
  19. package/dist/components/data-grid/data-grid.js +6 -21
  20. package/dist/components/data-grid/data-grid.js.map +1 -1
  21. package/dist/components/data-grid/footer.js +9 -21
  22. package/dist/components/data-grid/footer.js.map +1 -1
  23. package/dist/components/data-grid/header.js +19 -18
  24. package/dist/components/data-grid/header.js.map +1 -1
  25. package/dist/components/data-grid/selection-cell.js +9 -23
  26. package/dist/components/data-grid/selection-cell.js.map +1 -1
  27. package/dist/components/fab.js +10 -5
  28. package/dist/components/fab.js.map +1 -1
  29. package/dist/components/grid.js +1 -10
  30. package/dist/components/grid.js.map +1 -1
  31. package/dist/components/inputs/autocomplete.js +3 -13
  32. package/dist/components/inputs/autocomplete.js.map +1 -1
  33. package/dist/components/inputs/input.js +31 -39
  34. package/dist/components/inputs/input.js.map +1 -1
  35. package/dist/components/inputs/text-area.js +3 -11
  36. package/dist/components/inputs/text-area.js.map +1 -1
  37. package/dist/components/loader.js +8 -14
  38. package/dist/components/loader.js.map +1 -1
  39. package/dist/components/modal.js +7 -17
  40. package/dist/components/modal.js.map +1 -1
  41. package/dist/components/noty-list.js +27 -29
  42. package/dist/components/noty-list.js.map +1 -1
  43. package/dist/components/paper.js +13 -17
  44. package/dist/components/paper.js.map +1 -1
  45. package/dist/components/suggest/index.js +14 -20
  46. package/dist/components/suggest/index.js.map +1 -1
  47. package/dist/components/suggest/suggest-input.js +9 -10
  48. package/dist/components/suggest/suggest-input.js.map +1 -1
  49. package/dist/components/suggest/suggest-manager.js +8 -2
  50. package/dist/components/suggest/suggest-manager.js.map +1 -1
  51. package/dist/components/suggest/suggestion-list.js +27 -30
  52. package/dist/components/suggest/suggestion-list.js.map +1 -1
  53. package/dist/components/tabs.js +32 -44
  54. package/dist/components/tabs.js.map +1 -1
  55. package/dist/components/wizard/index.js +4 -7
  56. package/dist/components/wizard/index.js.map +1 -1
  57. package/dist/services/collection-service.js +10 -0
  58. package/dist/services/collection-service.js.map +1 -1
  59. package/dist/services/default-variable-theme.js +80 -0
  60. package/dist/services/default-variable-theme.js.map +1 -0
  61. package/dist/services/index.js +1 -0
  62. package/dist/services/index.js.map +1 -1
  63. package/dist/services/theme-provider-service.js +9 -7
  64. package/dist/services/theme-provider-service.js.map +1 -1
  65. package/package.json +6 -6
  66. package/src/components/app-bar-link.tsx +29 -35
  67. package/src/components/app-bar.tsx +22 -30
  68. package/src/components/avatar.tsx +40 -36
  69. package/src/components/button.tsx +91 -81
  70. package/src/components/command-palette/command-palette-input.tsx +15 -13
  71. package/src/components/command-palette/command-palette-suggestion-list.tsx +50 -58
  72. package/src/components/command-palette/index.tsx +54 -52
  73. package/src/components/data-grid/body.tsx +10 -22
  74. package/src/components/data-grid/data-grid-row.tsx +64 -63
  75. package/src/components/data-grid/data-grid.tsx +13 -24
  76. package/src/components/data-grid/footer.tsx +12 -23
  77. package/src/components/data-grid/header.tsx +34 -30
  78. package/src/components/data-grid/selection-cell.tsx +9 -19
  79. package/src/components/fab.tsx +21 -23
  80. package/src/components/grid.tsx +2 -11
  81. package/src/components/inputs/autocomplete.tsx +10 -16
  82. package/src/components/inputs/input.tsx +37 -42
  83. package/src/components/inputs/text-area.tsx +4 -18
  84. package/src/components/loader.tsx +19 -13
  85. package/src/components/modal.tsx +9 -17
  86. package/src/components/noty-list.tsx +55 -54
  87. package/src/components/paper.tsx +19 -26
  88. package/src/components/suggest/index.tsx +19 -30
  89. package/src/components/suggest/suggest-input.tsx +22 -15
  90. package/src/components/suggest/suggest-manager.ts +15 -3
  91. package/src/components/suggest/suggestion-list.tsx +90 -85
  92. package/src/components/tabs.tsx +50 -70
  93. package/src/components/wizard/index.tsx +6 -13
  94. package/src/services/collection-service.ts +14 -0
  95. package/src/services/default-variable-theme.ts +83 -0
  96. package/src/services/index.ts +1 -0
  97. package/src/services/theme-provider-service.ts +11 -8
  98. package/tsconfig.json +1 -1
  99. package/tsconfig.tsbuildinfo +1 -1
  100. package/types/components/app-bar-link.d.ts +1 -1
  101. package/types/components/app-bar-link.d.ts.map +1 -1
  102. package/types/components/app-bar.d.ts +1 -1
  103. package/types/components/app-bar.d.ts.map +1 -1
  104. package/types/components/avatar.d.ts +2 -1
  105. package/types/components/avatar.d.ts.map +1 -1
  106. package/types/components/button.d.ts +1 -1
  107. package/types/components/button.d.ts.map +1 -1
  108. package/types/components/command-palette/command-palette-input.d.ts +1 -1
  109. package/types/components/command-palette/command-palette-input.d.ts.map +1 -1
  110. package/types/components/command-palette/command-palette-suggestion-list.d.ts +1 -1
  111. package/types/components/command-palette/command-palette-suggestion-list.d.ts.map +1 -1
  112. package/types/components/command-palette/index.d.ts +1 -5
  113. package/types/components/command-palette/index.d.ts.map +1 -1
  114. package/types/components/data-grid/body.d.ts +1 -5
  115. package/types/components/data-grid/body.d.ts.map +1 -1
  116. package/types/components/data-grid/data-grid-row.d.ts +1 -5
  117. package/types/components/data-grid/data-grid-row.d.ts.map +1 -1
  118. package/types/components/data-grid/data-grid.d.ts +5 -3
  119. package/types/components/data-grid/data-grid.d.ts.map +1 -1
  120. package/types/components/data-grid/footer.d.ts +1 -1
  121. package/types/components/data-grid/footer.d.ts.map +1 -1
  122. package/types/components/data-grid/header.d.ts +1 -1
  123. package/types/components/data-grid/header.d.ts.map +1 -1
  124. package/types/components/data-grid/selection-cell.d.ts +1 -1
  125. package/types/components/data-grid/selection-cell.d.ts.map +1 -1
  126. package/types/components/fab.d.ts +1 -1
  127. package/types/components/fab.d.ts.map +1 -1
  128. package/types/components/grid.d.ts +1 -1
  129. package/types/components/grid.d.ts.map +1 -1
  130. package/types/components/inputs/autocomplete.d.ts +1 -1
  131. package/types/components/inputs/autocomplete.d.ts.map +1 -1
  132. package/types/components/inputs/input.d.ts +2 -3
  133. package/types/components/inputs/input.d.ts.map +1 -1
  134. package/types/components/inputs/text-area.d.ts +1 -6
  135. package/types/components/inputs/text-area.d.ts.map +1 -1
  136. package/types/components/loader.d.ts +9 -1
  137. package/types/components/loader.d.ts.map +1 -1
  138. package/types/components/modal.d.ts +1 -1
  139. package/types/components/modal.d.ts.map +1 -1
  140. package/types/components/noty-list.d.ts +2 -2
  141. package/types/components/noty-list.d.ts.map +1 -1
  142. package/types/components/paper.d.ts +1 -1
  143. package/types/components/paper.d.ts.map +1 -1
  144. package/types/components/skeleton.d.ts +1 -1
  145. package/types/components/skeleton.d.ts.map +1 -1
  146. package/types/components/suggest/index.d.ts +1 -5
  147. package/types/components/suggest/index.d.ts.map +1 -1
  148. package/types/components/suggest/suggest-input.d.ts +1 -1
  149. package/types/components/suggest/suggest-input.d.ts.map +1 -1
  150. package/types/components/suggest/suggest-manager.d.ts +2 -1
  151. package/types/components/suggest/suggest-manager.d.ts.map +1 -1
  152. package/types/components/suggest/suggestion-list.d.ts +1 -1
  153. package/types/components/suggest/suggestion-list.d.ts.map +1 -1
  154. package/types/components/tabs.d.ts +2 -2
  155. package/types/components/tabs.d.ts.map +1 -1
  156. package/types/components/wizard/index.d.ts +2 -2
  157. package/types/components/wizard/index.d.ts.map +1 -1
  158. package/types/services/collection-service.d.ts +4 -0
  159. package/types/services/collection-service.d.ts.map +1 -1
  160. package/types/services/default-variable-theme.d.ts +7 -0
  161. package/types/services/default-variable-theme.d.ts.map +1 -0
  162. package/types/services/index.d.ts +1 -0
  163. package/types/services/index.d.ts.map +1 -1
  164. package/types/services/theme-provider-service.d.ts +2 -3
  165. package/types/services/theme-provider-service.d.ts.map +1 -1
@@ -19,37 +19,41 @@ export interface DataGridHeaderState<T> {
19
19
  export const DataGridHeader: <T, K extends keyof T>(
20
20
  props: DataGridHeaderProps<T, K>,
21
21
  children: ChildrenList,
22
- ) => JSX.Element<any, any> = Shade<DataGridHeaderProps<any, any>, DataGridHeaderState<any>>({
22
+ ) => JSX.Element<any> = Shade<DataGridHeaderProps<any, any>>({
23
23
  shadowDomName: 'data-grid-header',
24
- getInitialState: ({ props }) => ({
25
- querySettings: props.collectionService.querySettings.getValue(),
26
- isSearchOpened: false,
27
- updateSearchValue: debounce((value: string) => {
28
- const currentSettings = props.collectionService.querySettings.getValue()
29
- const newSettings: FindOptions<unknown, any> = {
30
- ...currentSettings,
31
- filter: {
32
- ...currentSettings.filter,
33
- [props.field]: { $regex: value },
34
- },
35
- }
36
- props.collectionService.querySettings.setValue(newSettings)
37
- }),
38
- }),
39
- constructed: ({ props, updateState, getState }) => {
40
- const disposable = props.collectionService.querySettings.subscribe((querySettings) =>
41
- updateState({ querySettings }, getState().isSearchOpened),
24
+ // getInitialState: ({ props }) => ({
25
+ // querySettings: props.collectionService.querySettings.getValue(),
26
+ // isSearchOpened: false,
27
+ // updateSearchValue: ,
28
+ // }),
29
+ render: ({ props, element, useState, useObservable }) => {
30
+ const [querySettings, setQuerySettings] = useObservable('querySettings', props.collectionService.querySettings)
31
+ const currentOrder = Object.keys(querySettings.order || {})[0]
32
+ const currentOrderDirection = Object.values(querySettings.order || {})[0]
33
+
34
+ const [isSearchOpened, setIsSearchOpened] = useState('isSearchOpened', false)
35
+ const [updateSearchValue] = useState(
36
+ 'updateSearchValue',
37
+ debounce((value: string) => {
38
+ const currentSettings = props.collectionService.querySettings.getValue()
39
+ const newSettings: FindOptions<unknown, any> = {
40
+ ...currentSettings,
41
+ filter: {
42
+ ...currentSettings.filter,
43
+ [props.field]: { $regex: value },
44
+ },
45
+ }
46
+ props.collectionService.querySettings.setValue(newSettings)
47
+ }),
42
48
  )
43
- return () => disposable.dispose()
44
- },
45
- render: ({ getState, props, updateState, element }) => {
46
- const currentState = getState()
47
- const currentOrder = Object.keys(currentState.querySettings.order || {})[0]
48
- const currentOrderDirection = Object.values(currentState.querySettings.order || {})[0]
49
49
 
50
50
  const filterValue = (props.collectionService.querySettings.getValue().filter as any)?.[props.field]?.$regex || ''
51
51
 
52
- if (currentState.isSearchOpened) {
52
+ useObservable('querySettingsChange', props.collectionService.querySettings, (newSettings) => {
53
+ setQuerySettings(newSettings)
54
+ })
55
+
56
+ if (isSearchOpened) {
53
57
  setTimeout(() => {
54
58
  element.querySelector('input')?.focus()
55
59
  }, 1)
@@ -59,8 +63,8 @@ export const DataGridHeader: <T, K extends keyof T>(
59
63
  value={filterValue}
60
64
  placeholder={props.field}
61
65
  autofocus
62
- onblur={() => updateState({ isSearchOpened: false })}
63
- onkeyup={(ev) => currentState.updateSearchValue((ev.target as HTMLInputElement).value)}
66
+ onblur={() => setIsSearchOpened(false)}
67
+ onkeyup={(ev) => updateSearchValue((ev.target as HTMLInputElement).value)}
64
68
  labelProps={{
65
69
  style: { padding: '0px 2em' },
66
70
  }}
@@ -71,7 +75,7 @@ export const DataGridHeader: <T, K extends keyof T>(
71
75
  return (
72
76
  <div
73
77
  style={{ display: 'flex', width: '100%', height: '100%', justifyContent: 'space-around' }}
74
- onclick={() => updateState({ isSearchOpened: true })}
78
+ onclick={() => setIsSearchOpened(true)}
75
79
  >
76
80
  <div>{props.field}</div>
77
81
  <div className="header-controls" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
@@ -88,7 +92,7 @@ export const DataGridHeader: <T, K extends keyof T>(
88
92
  }
89
93
  newOrder[props.field] = newDirection
90
94
  props.collectionService.querySettings.setValue({
91
- ...currentState.querySettings,
95
+ ...querySettings,
92
96
  order: newOrder,
93
97
  })
94
98
  }}
@@ -3,29 +3,19 @@ import type { CollectionService } from '../../services'
3
3
 
4
4
  export const SelectionCell = Shade<{ entry: any; service: CollectionService<any> }>({
5
5
  shadowDomName: 'shades-data-grid-selection-cell',
6
- resources: ({ props, element }) => [
7
- props.service.selection.subscribe((selection) => {
8
- if (selection.includes(props.entry)) {
9
- ;(element.firstChild as HTMLInputElement).checked = true
10
- } else {
11
- ;(element.firstChild as HTMLInputElement).checked = false
12
- }
13
- }),
14
- ],
15
- render: ({ props }) => {
6
+ render: ({ props, useObservable, element }) => {
7
+ const [selection] = useObservable('selection', props.service.selection, (newSelection) => {
8
+ ;(element.querySelector('input') as HTMLInputElement).checked = newSelection.includes(props.entry)
9
+ })
10
+ const isSelected = selection.includes(props.entry)
11
+
16
12
  return (
17
13
  <input
18
- onchange={(ev) => {
19
- if ((ev.target as HTMLInputElement).checked) {
20
- props.service.selection.setValue([...props.service.selection.getValue(), props.entry])
21
- } else {
22
- props.service.selection.setValue([
23
- ...props.service.selection.getValue().filter((entry) => entry !== props.entry),
24
- ])
25
- }
14
+ onchange={() => {
15
+ props.service.toggleSelection(props.entry)
26
16
  }}
27
17
  type="checkbox"
28
- checked={props.service.selection.getValue().includes(props.entry) ? true : false}
18
+ checked={isSelected}
29
19
  />
30
20
  )
31
21
  },
@@ -1,30 +1,28 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
+ import { attachProps } from '@furystack/shades'
2
3
  import { Shade, createComponent } from '@furystack/shades'
3
4
 
4
5
  export const Fab = Shade<PartialElement<HTMLDivElement>>({
5
6
  shadowDomName: 'shade-fab',
6
- render: ({ props, children }) => {
7
- return (
8
- <div
9
- {...props}
10
- style={{
11
- position: 'fixed',
12
- bottom: '32px',
13
- right: '32px',
14
- background: 'gray',
15
- width: '64px',
16
- height: '64px',
17
- display: 'flex',
18
- justifyContent: 'center',
19
- alignItems: 'center',
20
- borderRadius: '50%',
21
- boxShadow: '2px 2px 4px rgba(0,0,0,0.3)',
22
- cursor: 'pointer',
23
- ...props.style,
24
- }}
25
- >
26
- {children}
27
- </div>
28
- )
7
+ render: ({ props, children, element }) => {
8
+ attachProps(element, {
9
+ ...props,
10
+ style: {
11
+ position: 'fixed',
12
+ bottom: '32px',
13
+ right: '32px',
14
+ background: 'gray',
15
+ width: '64px',
16
+ height: '64px',
17
+ display: 'flex',
18
+ justifyContent: 'center',
19
+ alignItems: 'center',
20
+ borderRadius: '50%',
21
+ boxShadow: '2px 2px 4px rgba(0,0,0,0.3)',
22
+ cursor: 'pointer',
23
+ ...props?.style,
24
+ },
25
+ })
26
+ return <>{children}</>
29
27
  },
30
28
  })
@@ -23,19 +23,10 @@ export type RowCells<T> = {
23
23
  [TKey in keyof T | 'default']?: (element: T) => JSX.Element
24
24
  }
25
25
 
26
- export const Grid: <T>(props: GridProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade({
26
+ export const Grid: <T>(props: GridProps<T>, children: ChildrenList) => JSX.Element<any> = Shade({
27
27
  shadowDomName: 'shade-grid',
28
- resources: ({ injector, element }) => [
29
- injector.getInstance(ThemeProviderService).theme.subscribe((t) => {
30
- const headers = element.querySelectorAll('th')
31
- headers.forEach((header) => {
32
- header.style.color = t.text.secondary
33
- header.style.background = t.background.paper
34
- })
35
- }),
36
- ],
37
28
  render: ({ props, injector }) => {
38
- const theme = injector.getInstance(ThemeProviderService).theme.getValue()
29
+ const { theme } = injector.getInstance(ThemeProviderService)
39
30
  const headerStyle: Partial<CSSStyleDeclaration> = {
40
31
  padding: '0 0.51em',
41
32
  backgroundColor: theme.background.paper,
@@ -2,27 +2,21 @@ import { Shade, createComponent } from '@furystack/shades'
2
2
  import type { TextInputProps } from './input'
3
3
  import { Input } from './input'
4
4
 
5
- export const Autocomplete = Shade<
6
- { inputProps?: TextInputProps; suggestions: string[]; strict?: boolean; onchange?: (value: string) => void },
7
- { dataListId: string }
8
- >({
9
- getInitialState: () => ({
10
- dataListId: (Math.random() + 1).toString(36).substring(3),
11
- }),
12
- constructed: ({ getState, element }) => {
13
- const { dataListId } = getState()
14
- const input = element.querySelector('input')
15
- if (input) {
16
- input.setAttribute('list', dataListId)
17
- }
18
- },
5
+ export const Autocomplete = Shade<{
6
+ inputProps?: TextInputProps
7
+ suggestions: string[]
8
+ strict?: boolean
9
+ onchange?: (value: string) => void
10
+ }>({
19
11
  shadowDomName: 'shade-autocomplete',
20
- render: ({ props, getState }) => {
21
- const { dataListId } = getState()
12
+ render: ({ props, useState }) => {
13
+ const [dataListId] = useState('dataListId', (Math.random() + 1).toString(36).substring(3))
14
+
22
15
  return (
23
16
  <div>
24
17
  <Input
25
18
  {...props.inputProps}
19
+ list={dataListId as any}
26
20
  onchange={(ev) => {
27
21
  const { value } = ev.target as any
28
22
  if (props.strict) {
@@ -1,7 +1,8 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
2
  import { Shade, createComponent, attachStyles } from '@furystack/shades'
3
+ import { ObservableValue } from '@furystack/utils'
3
4
  import { ThemeProviderService } from '../..'
4
- import type { Palette, Theme } from '../../services'
5
+ import type { Palette } from '../../services'
5
6
 
6
7
  export interface TextInputProps extends PartialElement<HTMLInputElement> {
7
8
  /**
@@ -47,7 +48,6 @@ export interface TextInputProps extends PartialElement<HTMLInputElement> {
47
48
  }
48
49
 
49
50
  export type TextInputState = {
50
- theme: Theme
51
51
  value: string
52
52
  focused: boolean
53
53
  validity: ValidityState
@@ -69,12 +69,12 @@ const getLabelStyle = ({
69
69
  justifyContent: 'space-between',
70
70
  fontSize: '10px',
71
71
  color: props.disabled
72
- ? state.theme.text.disabled
72
+ ? themeProvider.theme.text.disabled
73
73
  : state.validity?.valid === false
74
- ? state.theme.palette.error.main
74
+ ? themeProvider.theme.palette.error.main
75
75
  : state.focused
76
- ? state.theme.text.primary
77
- : state.theme.text.secondary,
76
+ ? themeProvider.theme.text.primary
77
+ : themeProvider.theme.text.secondary,
78
78
  marginBottom: '1em',
79
79
  padding: '1em',
80
80
  borderRadius: '5px',
@@ -83,8 +83,8 @@ const getLabelStyle = ({
83
83
  ? themeProvider
84
84
  .getRgbFromColorString(
85
85
  state.validity?.valid === false
86
- ? state.theme.palette.error.main
87
- : state.theme.palette[props.defaultColor || 'primary'].main,
86
+ ? themeProvider.theme.palette.error.main
87
+ : themeProvider.theme.palette[props.defaultColor || 'primary'].main,
88
88
  )
89
89
  .update('a', state.focused ? 0.1 : 0.2)
90
90
  .toString()
@@ -93,10 +93,10 @@ const getLabelStyle = ({
93
93
  props.variant === 'outlined' || props.variant === 'contained'
94
94
  ? `0 0 0 1px ${
95
95
  state.validity?.valid === false
96
- ? state.theme.palette.error.main
96
+ ? themeProvider.theme.palette.error.main
97
97
  : state.focused
98
- ? state.theme.palette[props.defaultColor || 'primary'].main
99
- : state.theme.text.primary
98
+ ? themeProvider.theme.palette[props.defaultColor || 'primary'].main
99
+ : themeProvider.theme.text.primary
100
100
  }`
101
101
  : 'none',
102
102
  filter: props.disabled ? 'grayscale(100%)' : 'none',
@@ -107,37 +107,32 @@ const getLabelStyle = ({
107
107
  }
108
108
  }
109
109
 
110
- export const Input = Shade<TextInputProps, TextInputState>({
110
+ export const Input = Shade<TextInputProps>({
111
111
  shadowDomName: 'shade-input',
112
- getInitialState: ({ injector, props }) => ({
113
- theme: injector.getInstance(ThemeProviderService).theme.getValue(),
114
- value: props.value || '',
115
- focused: props.autofocus || false,
116
- validity: { valid: true } as ValidityState,
117
- }),
118
- resources: ({ injector, updateState }) => [
119
- injector.getInstance(ThemeProviderService).theme.subscribe((theme) => updateState({ theme })),
120
- ],
121
- compareState: ({ newState, element, props, injector }) => {
112
+ render: ({ props, injector, useObservable, element }) => {
122
113
  const themeProvider = injector.getInstance(ThemeProviderService)
123
- const label = element.querySelector('label') as HTMLLabelElement
124
- attachStyles(label, { style: getLabelStyle({ themeProvider, props, state: newState }) })
125
114
 
126
- const helper = element.querySelector<HTMLSpanElement>('span.helperText')
127
- const helperNode = props.getHelperText?.({ state: newState }) || ''
128
- helper?.replaceChildren(helperNode)
115
+ const [state, setState] = useObservable<TextInputState>(
116
+ 'inputState',
117
+ new ObservableValue({
118
+ value: props.value || '',
119
+ focused: props.autofocus || false,
120
+ validity: { valid: true } as ValidityState,
121
+ }),
122
+ (newState) => {
123
+ const label = element.querySelector('label') as HTMLLabelElement
124
+ attachStyles(label, { style: getLabelStyle({ themeProvider, props, state: newState }) })
129
125
 
130
- const startIcon = element.querySelector<HTMLSpanElement>('span.startIcon')
131
- startIcon?.replaceChildren(props.getStartIcon?.({ state: newState }) || '')
132
- const endIcon = element.querySelector<HTMLSpanElement>('span.endIcon')
133
- endIcon?.replaceChildren(props.getEndIcon?.({ state: newState }) || '')
126
+ const helper = element.querySelector<HTMLSpanElement>('span.helperText')
127
+ const helperNode = props.getHelperText?.({ state: newState }) || ''
128
+ helper?.replaceChildren(helperNode)
134
129
 
135
- return false
136
- },
137
- render: ({ props, getState, updateState, injector }) => {
138
- const state = getState()
139
- const { value } = state
140
- const themeProvider = injector.getInstance(ThemeProviderService)
130
+ const startIcon = element.querySelector<HTMLSpanElement>('span.startIcon')
131
+ startIcon?.replaceChildren(props.getStartIcon?.({ state: newState }) || '')
132
+ const endIcon = element.querySelector<HTMLSpanElement>('span.endIcon')
133
+ endIcon?.replaceChildren(props.getEndIcon?.({ state: newState }) || '')
134
+ },
135
+ )
141
136
 
142
137
  return (
143
138
  <label {...props.labelProps} style={getLabelStyle({ props, state, themeProvider })}>
@@ -155,20 +150,20 @@ export const Input = Shade<TextInputProps, TextInputState>({
155
150
  oninvalid={(ev) => {
156
151
  ev.preventDefault()
157
152
  const el = ev.target as HTMLInputElement
158
- updateState({ validity: el.validity })
153
+ setState({ ...state, validity: el.validity })
159
154
  }}
160
155
  onchange={(ev) => {
161
156
  const el = ev.target as HTMLInputElement
162
157
  const newValue = el.value
163
- updateState({ value: newValue, validity: el?.validity })
158
+ setState({ ...state, value: newValue, validity: el?.validity })
164
159
  props.onTextChange?.(newValue)
165
160
  props.onchange && (props.onchange as any)(ev)
166
161
  }}
167
162
  onfocus={() => {
168
- updateState({ focused: true })
163
+ setState({ ...state, focused: true })
169
164
  }}
170
165
  onblur={() => {
171
- updateState({ focused: false })
166
+ setState({ ...state, focused: false })
172
167
  }}
173
168
  {...props}
174
169
  style={{
@@ -185,7 +180,7 @@ export const Input = Shade<TextInputProps, TextInputState>({
185
180
  flexGrow: '1',
186
181
  ...props.style,
187
182
  }}
188
- value={value}
183
+ value={state.value}
189
184
  />
190
185
  {props.getEndIcon ? <span className="endIcon">{props.getEndIcon({ state })}</span> : null}
191
186
  </div>
@@ -1,6 +1,5 @@
1
1
  import type { PartialElement } from '@furystack/shades'
2
2
  import { createComponent, Shade } from '@furystack/shades'
3
- import type { Theme } from '../../services'
4
3
  import { ThemeProviderService } from '../../services'
5
4
  import { promisifyAnimation } from '../../utils'
6
5
 
@@ -11,24 +10,11 @@ export interface TextAreaProps extends PartialElement<HTMLTextAreaElement> {
11
10
  variant?: 'contained' | 'outlined'
12
11
  }
13
12
 
14
- export type TextAreaInputState = {
15
- theme: Theme
16
- value?: string
17
- }
18
-
19
- export const TextArea = Shade<TextAreaProps, TextAreaInputState>({
13
+ export const TextArea = Shade<TextAreaProps>({
20
14
  shadowDomName: 'shade-text-area',
21
- getInitialState: ({ injector, props }) => ({
22
- theme: injector.getInstance(ThemeProviderService).theme.getValue(),
23
- value: props.value,
24
- }),
25
- resources: ({ injector, updateState }) => {
26
- const themeProvider = injector.getInstance(ThemeProviderService)
27
- return [themeProvider.theme.subscribe((theme) => updateState({ theme }))]
28
- },
29
- render: ({ props, element, injector, getState }) => {
15
+ render: ({ props, element, injector }) => {
30
16
  const themeProvider = injector.getInstance(ThemeProviderService)
31
- const { theme, value } = getState()
17
+ const { theme } = themeProvider
32
18
  const { palette } = theme
33
19
 
34
20
  return (
@@ -111,7 +97,7 @@ export const TextArea = Shade<TextAreaProps, TextAreaInputState>({
111
97
  }
112
98
  }}
113
99
  >
114
- {value}
100
+ {props.value}
115
101
  </div>
116
102
  </label>
117
103
  )
@@ -11,23 +11,29 @@ interface LoaderProps {
11
11
  * The time to wait before the loader shows up
12
12
  */
13
13
  delay?: number
14
+
15
+ /**
16
+ * The color of the loader
17
+ */
18
+ borderColor?: string
19
+
20
+ /**
21
+ * The width of the border
22
+ */
23
+ borderWidth?: number
14
24
  }
15
25
 
16
26
  export const Loader = Shade<LoaderProps>({
17
27
  shadowDomName: 'shade-loader',
18
- resources: ({ injector, element }) => [
19
- injector.getInstance(ThemeProviderService).theme.subscribe((theme) => {
20
- const el = element.firstElementChild
21
- if (el) {
22
- ;(el as HTMLElement).style.borderBottom = `15px solid ${theme.palette.primary.main}`
23
- }
24
- }, true),
25
- ],
26
- render: ({ element, props }) => {
28
+ render: ({ element, props, injector }) => {
29
+ const { theme } = injector.getInstance(ThemeProviderService)
30
+
27
31
  element.style.display = 'inline-block'
28
32
  element.style.transformOrigin = 'center'
29
33
  element.style.opacity = '0'
30
34
  const { delay = 500 } = props
35
+ const { borderWidth = 15 } = props
36
+ const { borderColor = theme.palette.primary.main } = props
31
37
 
32
38
  setTimeout(() => {
33
39
  promisifyAnimation(element, [{ opacity: '0' }, { opacity: '1' }], {
@@ -49,10 +55,10 @@ export const Loader = Shade<LoaderProps>({
49
55
  <div
50
56
  style={{
51
57
  position: 'relative',
52
- width: 'calc(100% - 30px)',
53
- height: 'calc(100% - 30px)',
54
- border: '15px solid rgba(128,128,128,0.1)',
55
- borderBottom: '15px solid red',
58
+ width: `calc(100% - ${borderWidth * 2}px)`,
59
+ height: `calc(100% - ${borderWidth * 2}px)`,
60
+ border: `${borderWidth}px solid rgba(128,128,128,0.1)`,
61
+ borderBottom: `${borderWidth}px solid ${borderColor}`,
56
62
  borderRadius: '50%',
57
63
  }}
58
64
  />
@@ -9,28 +9,20 @@ export type ModalProps = {
9
9
  hideAnimation?: (el: Element | null) => Promise<unknown>
10
10
  }
11
11
 
12
- export const Modal = Shade<ModalProps, { isVisible?: boolean }>({
13
- getInitialState: ({ props }) => ({ isVisible: props.isVisible.getValue() }),
12
+ export const Modal = Shade<ModalProps>({
14
13
  shadowDomName: 'shade-modal',
15
- resources: ({ props, element, updateState }) => [
16
- props.isVisible.subscribe(async (visible) => {
17
- if (visible) {
18
- updateState({ isVisible: visible })
19
- await props.showAnimation?.(element)
20
- } else {
21
- props.hideAnimation
22
- ? await props.hideAnimation?.(element).finally(() => updateState({ isVisible: visible }))
23
- : updateState({ isVisible: visible })
24
- }
25
- }),
26
- ],
14
+ render: ({ props, children, useObservable, element }) => {
15
+ const [isVisible] = useObservable('isVisible', props.isVisible)
16
+
17
+ if (isVisible) {
18
+ props.showAnimation?.(element)
19
+ }
27
20
 
28
- render: ({ props, getState, children }) => {
29
- const { isVisible } = getState()
30
21
  return isVisible ? (
31
22
  <div
32
23
  className="shade-backdrop"
33
- onclick={() => {
24
+ onclick={async () => {
25
+ await props.hideAnimation?.(element)
34
26
  props.onClose?.()
35
27
  }}
36
28
  style={{