@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
@@ -1,66 +1,57 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
2
  import { promisifyAnimation } from '../../utils/promisify-animation'
3
- import type { CommandPaletteSuggestionResult } from './command-provider'
4
3
  import type { CommandPaletteManager } from './command-palette-manager'
5
4
  import { ThemeProviderService } from '../../services/theme-provider-service'
6
5
 
7
- export const CommandPaletteSuggestionList = Shade<
8
- { manager: CommandPaletteManager; fullScreenSuggestions?: boolean },
9
- { suggestions: CommandPaletteSuggestionResult[] }
10
- >({
6
+ export const CommandPaletteSuggestionList = Shade<{ manager: CommandPaletteManager; fullScreenSuggestions?: boolean }>({
11
7
  shadowDomName: 'shade-command-palette-suggestion-list',
12
- getInitialState: ({ props }) => ({
13
- suggestions: props.manager.currentSuggestions.getValue(),
14
- }),
15
- constructed: ({ updateState, element, props }) => {
8
+ render: ({ element, injector, props, useObservable }) => {
16
9
  const { manager } = props
17
- const subscriptions = [
18
- manager.currentSuggestions.subscribe((suggestions) => {
19
- updateState({ suggestions })
20
- }),
21
- manager.isOpened.subscribe(async (isOpened) => {
22
- const container = element.firstElementChild as HTMLDivElement
23
- if (isOpened) {
24
- container.style.display = 'initial'
25
- container.style.zIndex = '1'
26
- container.style.width = `calc(${Math.round(
27
- element.parentElement?.getBoundingClientRect().width || 200,
28
- )}px - 3em)`
29
- await promisifyAnimation(
30
- container,
31
- [
32
- { opacity: 0, transform: 'translate(0, -50px)' },
33
- { opacity: 1, transform: 'translate(0, 0)' },
34
- ],
35
- { fill: 'forwards', duration: 500 },
36
- )
10
+ const { theme } = injector.getInstance(ThemeProviderService)
11
+
12
+ const [suggestions] = useObservable('suggestions', props.manager.currentSuggestions)
13
+ const [selectedIndex] = useObservable('selectedIndex', props.manager.selectedIndex, (idx) => {
14
+ ;([...element.querySelectorAll('.suggestion-item')] as HTMLDivElement[]).map((s, i) => {
15
+ if (i === idx) {
16
+ s.style.background = theme.background.paper
17
+ s.style.fontWeight = 'bolder'
37
18
  } else {
38
- await promisifyAnimation(
39
- container,
40
- [
41
- { opacity: 1, transform: 'translate(0, 0)' },
42
- { opacity: 0, transform: 'translate(0, -50px)' },
43
- ],
44
- { fill: 'forwards', duration: 200 },
45
- )
46
- container.style.zIndex = '-1'
47
- container.style.display = 'none'
19
+ s.style.background = theme.background.default
20
+ s.style.fontWeight = 'normal'
48
21
  }
49
- }),
50
- manager.selectedIndex.subscribe((idx) => {
51
- ;[...element.querySelectorAll('.suggestion-item')].map((s, i) => {
52
- if (i === idx) {
53
- ;(s as HTMLDivElement).style.background = 'rgba(128,128,128,0.2)'
54
- } else {
55
- ;(s as HTMLDivElement).style.background = 'rgba(96,96,96,0.2)'
56
- }
57
- })
58
- }),
59
- ]
60
- return () => subscriptions.map((s) => s.dispose())
61
- },
62
- render: ({ element, injector, getState, props }) => {
63
- const { manager } = props
22
+ })
23
+ })
24
+
25
+ const [isOpenedAtRender] = useObservable('isOpenedAtRender', props.manager.isOpened, async (isOpened) => {
26
+ const container = element.firstElementChild as HTMLDivElement
27
+ if (isOpened) {
28
+ container.style.display = 'initial'
29
+ container.style.zIndex = '1'
30
+ container.style.width = `calc(${Math.round(
31
+ element.parentElement?.getBoundingClientRect().width || 200,
32
+ )}px - 3em)`
33
+ await promisifyAnimation(
34
+ container,
35
+ [
36
+ { opacity: 0, transform: 'translate(0, -50px)' },
37
+ { opacity: 1, transform: 'translate(0, 0)' },
38
+ ],
39
+ { fill: 'forwards', duration: 500 },
40
+ )
41
+ } else {
42
+ await promisifyAnimation(
43
+ container,
44
+ [
45
+ { opacity: 1, transform: 'translate(0, 0)' },
46
+ { opacity: 0, transform: 'translate(0, -50px)' },
47
+ ],
48
+ { fill: 'forwards', duration: 200 },
49
+ )
50
+ container.style.zIndex = '-1'
51
+ container.style.display = 'none'
52
+ }
53
+ })
54
+
64
55
  return (
65
56
  <div
66
57
  className="suggestion-items-container"
@@ -75,22 +66,23 @@ export const CommandPaletteSuggestionList = Shade<
75
66
  maxHeight: `${window.innerHeight * 0.8}px`,
76
67
  zIndex: '1',
77
68
  left: 'auto',
78
- backgroundColor: injector.getInstance(ThemeProviderService).theme.getValue().background.paper,
69
+ backgroundColor: theme.background.paper,
79
70
  boxShadow: '3px 3px 5px rgba(0,0,0,0.3)',
80
71
  width: `calc(${Math.round(element.parentElement?.getBoundingClientRect().width || 200)}px - 3em)`,
81
72
  ...(props.fullScreenSuggestions ? { left: '0', width: 'calc(100% - 42px)' } : {}),
82
73
  }}
83
74
  >
84
- {getState().suggestions.map((s, i) => (
75
+ {suggestions.map((s, i) => (
85
76
  <div
86
77
  className="suggestion-item"
87
78
  onclick={() => {
88
- manager.isOpened.getValue() && manager.selectSuggestion(injector, i)
79
+ isOpenedAtRender && manager.selectSuggestion(injector, i)
89
80
  }}
90
81
  style={{
91
82
  padding: '1em',
92
83
  cursor: 'default',
93
- background: i === manager.selectedIndex.getValue() ? 'rgba(128,128,128,0.2)' : 'transparent',
84
+ background: i === selectedIndex ? theme.background.paper : theme.background.default,
85
+ fontWeight: i === selectedIndex ? 'bolder' : 'normal',
94
86
  }}
95
87
  >
96
88
  {s.element}
@@ -6,6 +6,7 @@ import { CommandPaletteInput } from './command-palette-input'
6
6
  import { CommandPaletteSuggestionList } from './command-palette-suggestion-list'
7
7
  import type { CommandProvider } from './command-provider'
8
8
  import { ClickAwayService } from '../../services/click-away-service'
9
+ import { ThemeProviderService } from '../../services'
9
10
 
10
11
  export * from './command-palette-input'
11
12
  export * from './command-palette-manager'
@@ -19,23 +20,35 @@ export interface CommandPaletteProps {
19
20
  fullScreenSuggestions?: boolean
20
21
  }
21
22
 
22
- export interface CommandPaletteState {
23
- manager: CommandPaletteManager
24
- }
25
-
26
- export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
23
+ export const CommandPalette = Shade<CommandPaletteProps>({
27
24
  shadowDomName: 'shade-command-palette',
28
- getInitialState: ({ props }) => ({
29
- manager: new CommandPaletteManager(props.commandProviders),
30
- }),
31
- resources: ({ getState, element: rootElement }) => {
32
- const { manager } = getState()
33
- const element = rootElement.querySelector('.input-container') as HTMLDivElement
34
- const clickAwayListener = new ClickAwayService(rootElement, () => manager.isOpened.setValue(false))
35
- return [
36
- manager.isOpened.subscribe((isOpened) => {
37
- const suggestions = rootElement.querySelector('.close-suggestions')
38
- const postControls = rootElement.querySelector('.post-controls')
25
+ render: ({ props, injector, element, useState, useDisposable, useObservable }) => {
26
+ element.style.flexGrow = '1'
27
+ const [manager] = useState('manager', new CommandPaletteManager(props.commandProviders))
28
+ const { theme } = injector.getInstance(ThemeProviderService)
29
+
30
+ useDisposable('clickAwayService', () => new ClickAwayService(element, () => manager.isOpened.setValue(false)))
31
+
32
+ const [isLoadingAtRender] = useObservable('isLoading', manager.isLoading, async (isLoading) => {
33
+ const loader = element.querySelector('.loader-container')
34
+ if (isLoading) {
35
+ promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
36
+ duration: 100,
37
+ fill: 'forwards',
38
+ })
39
+ } else {
40
+ promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
41
+ duration: 100,
42
+ fill: 'forwards',
43
+ })
44
+ }
45
+ })
46
+
47
+ const [isOpenedAtRender, setIsOpened] = useObservable('isOpened', manager.isOpened, (isOpened) => {
48
+ {
49
+ const suggestions = element.querySelector('.close-suggestions')
50
+ const postControls = element.querySelector('.post-controls')
51
+ const inputContainer = element.querySelector('.input-container') as HTMLDivElement
39
52
  if (isOpened) {
40
53
  promisifyAnimation(suggestions, [{ opacity: 0 }, { opacity: 1 }], {
41
54
  duration: 500,
@@ -47,11 +60,15 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
47
60
  fill: 'forwards',
48
61
  })
49
62
 
50
- promisifyAnimation(element, [{ background: 'transparent' }, { background: 'rgba(128,128,128,0.1)' }], {
51
- duration: 500,
52
- fill: 'forwards',
53
- easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
54
- })
63
+ promisifyAnimation(
64
+ inputContainer,
65
+ [{ background: 'transparent' }, { background: theme.background.default }],
66
+ {
67
+ duration: 500,
68
+ fill: 'forwards',
69
+ easing: 'cubic-bezier(0.050, 0.570, 0.840, 1.005)',
70
+ },
71
+ )
55
72
  } else {
56
73
  promisifyAnimation(suggestions, [{ opacity: 1 }, { opacity: 0 }], {
57
74
  duration: 500,
@@ -64,34 +81,19 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
64
81
  delay: 300,
65
82
  })
66
83
 
67
- promisifyAnimation(element, [{ background: 'rgba(128,128,128,0.1)' }, { background: 'transparent' }], {
68
- duration: 300,
69
- fill: 'forwards',
70
- easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
71
- })
84
+ promisifyAnimation(
85
+ inputContainer,
86
+ [{ background: theme.background.default }, { background: 'transparent' }],
87
+ {
88
+ duration: 300,
89
+ fill: 'forwards',
90
+ easing: 'cubic-bezier(0.000, 0.245, 0.190, 0.790)',
91
+ },
92
+ )
72
93
  }
73
- }),
74
- manager.isLoading.subscribe(async (isLoading) => {
75
- const loader = rootElement.querySelector('.loader-container')
76
- if (isLoading) {
77
- promisifyAnimation(loader, [{ opacity: 0 }, { opacity: 1 }], {
78
- duration: 100,
79
- fill: 'forwards',
80
- })
81
- } else {
82
- promisifyAnimation(loader, [{ opacity: 1 }, { opacity: 0 }], {
83
- duration: 100,
84
- fill: 'forwards',
85
- })
86
- }
87
- }),
88
- clickAwayListener,
89
- manager,
90
- ]
91
- },
92
- render: ({ props, injector, element, getState }) => {
93
- element.style.flexGrow = '1'
94
- const { manager } = getState()
94
+ }
95
+ })
96
+
95
97
  return (
96
98
  <div
97
99
  style={{ display: 'flex', flexDirection: 'column' }}
@@ -135,7 +137,7 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
135
137
  fontWeight: 'bolder',
136
138
  textShadow: '0 0 1px #aaa',
137
139
  }}
138
- onclick={() => manager.isOpened.setValue(true)}
140
+ onclick={() => setIsOpened(true)}
139
141
  >
140
142
  {props.defaultPrefix}
141
143
  </div>
@@ -146,19 +148,19 @@ export const CommandPalette = Shade<CommandPaletteProps, CommandPaletteState>({
146
148
  display: 'flex',
147
149
  alignItems: 'center',
148
150
  justifyContent: 'space-between',
149
- width: manager.isOpened.getValue() ? '50px' : '0px',
151
+ width: isOpenedAtRender ? '50px' : '0px',
150
152
  overflow: 'hidden',
151
153
  }}
152
154
  >
153
155
  <div
154
156
  className="loader-container"
155
- style={{ width: '20px', height: '20px', opacity: manager.isLoading.getValue() ? '1' : '0' }}
157
+ style={{ width: '20px', height: '20px', opacity: isLoadingAtRender ? '1' : '0' }}
156
158
  >
157
159
  <Loader style={{ width: '100%', height: '100%' }} />
158
160
  </div>
159
161
  <div
160
162
  className="close-suggestions"
161
- onclick={() => manager.isOpened.setValue(false)}
163
+ onclick={() => setIsOpened(false)}
162
164
  style={{
163
165
  width: '20px',
164
166
  height: '20px',
@@ -1,6 +1,6 @@
1
1
  import type { CollectionService } from '../../services/collection-service'
2
2
  import type { ChildrenList } from '@furystack/shades'
3
- import { Shade, createComponent, createFragment } from '@furystack/shades'
3
+ import { Shade, createComponent } from '@furystack/shades'
4
4
  import type { DataRowCells } from './data-grid'
5
5
  import { Loader } from '../loader'
6
6
  import { DataGridRow } from './data-grid-row'
@@ -20,29 +20,17 @@ export interface DataGridBodyProps<T> {
20
20
  loaderComponent?: JSX.Element
21
21
  }
22
22
 
23
- export interface DataGridBodyState<T> {
24
- data: T[]
25
- isLoading: boolean
26
- }
27
-
28
- export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
29
- DataGridBodyProps<any>,
30
- DataGridBodyState<any>
23
+ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
24
+ DataGridBodyProps<any>
31
25
  >({
32
- getInitialState: ({ props }) => ({
33
- data: props.service.data.getValue().entries,
34
- isLoading: props.service.isLoading.getValue(),
35
- }),
36
- resources: ({ props, updateState }) => [
37
- props.service.data.subscribe((data) => updateState({ data: data.entries })),
38
- props.service.isLoading.subscribe((isLoading) => updateState({ isLoading })),
39
- ],
40
26
  shadowDomName: 'shade-data-grid-body',
41
- render: ({ getState, props, element }) => {
27
+ render: ({ props, element, useObservable }) => {
42
28
  element.style.display = 'table-row-group'
43
- const state = getState()
44
29
 
45
- if (state.isLoading) {
30
+ const [data] = useObservable('data', props.service.data)
31
+ const [isLoading] = useObservable('isLoading', props.service.isLoading)
32
+
33
+ if (isLoading) {
46
34
  return (
47
35
  <div style={{ display: 'flex', height: '100%', justifyContent: 'center', alignItems: 'center', width: '100%' }}>
48
36
  {/* TODO: Skeleton */}
@@ -51,13 +39,13 @@ export const DataGridBody: <T>(props: DataGridBodyProps<T>, children: ChildrenLi
51
39
  )
52
40
  }
53
41
 
54
- if (!state.data?.length) {
42
+ if (!data?.entries?.length) {
55
43
  return props.emptyComponent || <div> - No Data - </div>
56
44
  }
57
45
 
58
46
  return (
59
47
  <>
60
- {state.data.map((entry) => (
48
+ {data?.entries?.map((entry) => (
61
49
  <DataGridRow<any>
62
50
  columns={props.columns}
63
51
  entry={entry}
@@ -1,5 +1,6 @@
1
1
  import type { ChildrenList } from '@furystack/shades'
2
- import { attachStyles, createComponent, createFragment, Shade } from '@furystack/shades'
2
+ import { attachStyles, createComponent, Shade } from '@furystack/shades'
3
+ import { ThemeProviderService } from '../../services'
3
4
  import type { CollectionService } from '../../services/collection-service'
4
5
  import type { DataRowCells } from './data-grid'
5
6
 
@@ -16,87 +17,88 @@ export interface DataGridRowProps<T> {
16
17
  unselectedRowStyle?: Partial<CSSStyleDeclaration>
17
18
  }
18
19
 
19
- export interface DataGridRowState<T> {
20
- selection?: T[]
21
- focus?: T
22
- }
23
-
24
- export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
25
- DataGridRowProps<any>,
26
- DataGridRowState<any>
20
+ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
21
+ DataGridRowProps<any>
27
22
  >({
28
- getInitialState: ({ props }) => ({
29
- focus: props.service.focusedEntry.getValue(),
30
- selection: props.service.selection.getValue(),
31
- }),
32
23
  shadowDomName: 'shades-data-grid-row',
33
- resources: ({ props, element }) => [
34
- props.service.focusedEntry.subscribe((newEntry) => {
35
- if (newEntry === props.entry) {
36
- attachStyles(element, {
37
- style: props.focusedRowStyle || {
38
- filter: 'brightness(1.5)',
39
- fontWeight: 'bolder',
40
- },
41
- })
42
24
 
43
- element.classList.add('focused')
25
+ render: ({ props, element, useObservable, injector }) => {
26
+ const { entry, rowComponents, columns, service } = props
44
27
 
45
- const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
28
+ const { theme } = injector.getInstance(ThemeProviderService)
46
29
 
47
- const parent = element.closest('.shade-grid-wrapper') as HTMLElement
48
- const maxTop = element.offsetTop - headerHeight
49
- const currentTop = parent.scrollTop
50
- if (maxTop < currentTop) {
51
- parent.scrollTo({ top: maxTop, behavior: 'smooth' })
52
- }
53
-
54
- const footerHeight =
55
- element.closest('shade-data-grid')?.querySelector('shade-data-grid-footer')?.getBoundingClientRect().height ||
56
- 42
57
- const visibleMaxTop = parent.clientHeight - footerHeight
58
- const desiredMaxTop = element.offsetTop + element.clientHeight
59
- if (desiredMaxTop > visibleMaxTop) {
60
- parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
61
- }
62
- } else {
63
- element.classList.remove('focused')
64
- attachStyles(element, {
65
- style: props.unfocusedRowStyle || {
66
- filter: 'brightness(1)',
67
- fontWeight: 'inherit',
68
- },
69
- })
70
- }
71
- }, true),
72
- props.service.selection.subscribe((selection) => {
73
- if (selection.includes(props.entry)) {
30
+ const attachSelectedStyles = (selection: any[]) => {
31
+ if (selection.includes(entry)) {
74
32
  element.classList.add('selected')
75
- attachStyles(element, { style: props.selectedRowStyle || { backgroundColor: 'rgba(128,128,128,0.1)' } })
33
+ attachStyles(element, { style: props.selectedRowStyle || { backgroundColor: theme.background.default } })
76
34
  element.setAttribute('aria-selected', 'true')
77
35
  } else {
78
36
  element.classList.remove('selected')
79
37
  attachStyles(element, { style: props.unselectedRowStyle || { backgroundColor: 'transparent' } })
80
38
  element.setAttribute('aria-selected', 'false')
81
39
  }
82
- }, true),
83
- ],
40
+ }
41
+
42
+ const [selection] = useObservable('isSelected', service.selection, attachSelectedStyles, true)
43
+
44
+ attachSelectedStyles(selection)
45
+
46
+ const [focus] = useObservable(
47
+ 'focus',
48
+ service.focusedEntry,
49
+ (newEntry) => {
50
+ if (newEntry === props.entry) {
51
+ attachStyles(element, {
52
+ style: props.focusedRowStyle || {
53
+ boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
54
+ fontWeight: 'bolder',
55
+ },
56
+ })
57
+
58
+ element.classList.add('focused')
59
+
60
+ const headerHeight = element.closest('table')?.querySelector('th')?.getBoundingClientRect().height || 42
61
+
62
+ const parent = element.closest('.shade-grid-wrapper') as HTMLElement
63
+ const maxTop = element.offsetTop - headerHeight
64
+ const currentTop = parent.scrollTop
65
+ if (maxTop < currentTop) {
66
+ parent.scrollTo({ top: maxTop, behavior: 'smooth' })
67
+ }
68
+
69
+ const footerHeight =
70
+ element.closest('shade-data-grid')?.querySelector('shade-data-grid-footer')?.getBoundingClientRect()
71
+ .height || 42
72
+ const visibleMaxTop = parent.clientHeight - footerHeight
73
+ const desiredMaxTop = element.offsetTop + element.clientHeight
74
+ if (desiredMaxTop > visibleMaxTop) {
75
+ parent.scrollTo({ top: desiredMaxTop - visibleMaxTop, behavior: 'smooth' })
76
+ }
77
+ } else {
78
+ element.classList.remove('focused')
79
+ attachStyles(element, {
80
+ style: props.unfocusedRowStyle || {
81
+ boxShadow: 'none',
82
+ fontWeight: 'inherit',
83
+ },
84
+ })
85
+ }
86
+ },
87
+ true,
88
+ )
84
89
 
85
- render: ({ getState, props, element }) => {
86
- const state = getState()
87
- const { entry, rowComponents, columns } = props
88
90
  element.style.display = 'table-row'
89
91
  element.style.cursor = 'default'
90
92
  element.style.userSelect = 'none'
91
- if (state.selection?.includes(entry)) {
93
+ if (selection?.includes(entry)) {
92
94
  element.setAttribute('aria-selected', 'true')
93
95
  element.classList.add('selected')
94
96
  }
95
97
 
96
- if (state.focus === entry) {
98
+ if (focus === entry) {
97
99
  element.classList.add('focused')
98
100
  }
99
- element.setAttribute('aria-selected', state.selection?.includes(entry).toString() || 'false')
101
+ element.setAttribute('aria-selected', selection?.includes(entry).toString() || 'false')
100
102
 
101
103
  return (
102
104
  <>
@@ -106,9 +108,8 @@ export const DataGridRow: <T>(props: DataGridRowProps<T>, children: ChildrenList
106
108
  onclick={(ev) => props.onRowClick?.(entry, ev)}
107
109
  ondblclick={(ev) => props.onRowDoubleClick?.(entry, ev)}
108
110
  >
109
- {rowComponents?.[column]?.(entry, state) || rowComponents?.default?.(entry, state) || (
110
- <span>{entry[column]}</span>
111
- )}
111
+ {rowComponents?.[column]?.(entry, { selection, focus }) ||
112
+ rowComponents?.default?.(entry, { selection, focus }) || <span>{entry[column]}</span>}
112
113
  </td>
113
114
  ))}
114
115
  </>
@@ -6,13 +6,12 @@ import { DataGridHeader } from './header'
6
6
  import { DataGridBody } from './body'
7
7
  import { DataGridFooter } from './footer'
8
8
  import { ClickAwayService, ThemeProviderService } from '../../services'
9
- import type { DataGridRowState } from './data-grid-row'
10
9
 
11
10
  export type DataHeaderCells<T> = {
12
11
  [TKey in keyof T | 'default']?: (name: keyof T) => JSX.Element
13
12
  }
14
13
  export type DataRowCells<T> = {
15
- [TKey in keyof T | 'default']?: (element: T, state: DataGridRowState<T>) => JSX.Element
14
+ [TKey in keyof T | 'default']?: (element: T, state: { focus?: T; selection: T[] }) => JSX.Element
16
15
  }
17
16
 
18
17
  export interface DataGridProps<T> {
@@ -68,39 +67,29 @@ export interface DataGridProps<T> {
68
67
  loaderComponent?: JSX.Element
69
68
  }
70
69
 
71
- export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => JSX.Element<any, any> = Shade<
70
+ export const DataGrid: <T>(props: DataGridProps<T>, children: ChildrenList) => JSX.Element<any> = Shade<
72
71
  DataGridProps<any>
73
72
  >({
74
73
  shadowDomName: 'shade-data-grid',
75
- resources: ({ injector, element, props }) => {
76
- const tp = injector.getInstance(ThemeProviderService)
77
- return [
78
- tp.theme.subscribe((t) => {
79
- const headers = element.querySelectorAll('th')
80
- const { r, g, b } = tp.getRgbFromColorString(t.background.paper)
81
- headers.forEach((header) => {
82
- header.style.color = t.text.secondary
83
- header.style.backgroundColor = `rgba(${r}, ${g}, ${b}, 0.3)`
84
- })
85
- }),
86
- new ClickAwayService(element, () => {
87
- props.service.hasFocus.setValue(false)
88
- }),
89
- ]
90
- },
91
74
  constructed: ({ props }) => {
92
75
  const listener = (ev: KeyboardEvent) => props.service.handleKeyDown(ev)
93
76
  window.addEventListener('keydown', listener)
94
77
  return () => window.removeEventListener('keydown', listener)
95
78
  },
96
- render: ({ props, injector }) => {
79
+ render: ({ props, injector, useDisposable, element }) => {
97
80
  const tp = injector.getInstance(ThemeProviderService)
98
- const theme = tp.theme.getValue()
81
+ const { theme } = tp
82
+
83
+ useDisposable(
84
+ 'clickAway',
85
+ () =>
86
+ new ClickAwayService(element, () => {
87
+ props.service.hasFocus.setValue(false)
88
+ }),
89
+ )
99
90
 
100
- const { r, g, b } = tp.getRgbFromColorString(theme.background.paper)
101
91
  const headerStyle: Partial<CSSStyleDeclaration> = {
102
- backgroundColor: `rgba(${r}, ${g}, ${b}, 0.3)`,
103
- backdropFilter: 'blur(10px)',
92
+ backdropFilter: 'blur(12px)',
104
93
  padding: '12px 0',
105
94
  color: theme.text.secondary,
106
95
  alignItems: 'center',
@@ -1,33 +1,22 @@
1
1
  import { Shade, createComponent } from '@furystack/shades'
2
2
  import { ThemeProviderService } from '../../services'
3
- import type { CollectionService, CollectionData } from '../../services/collection-service'
3
+ import type { CollectionService } from '../../services/collection-service'
4
4
 
5
5
  export const dataGridItemsPerPage = [10, 20, 25, 50, 100, Infinity]
6
6
 
7
- export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data: CollectionData<any> }>({
7
+ export const DataGridFooter = Shade<{ service: CollectionService<any> }>({
8
8
  shadowDomName: 'shade-data-grid-footer',
9
- getInitialState: ({ props }) => ({
10
- data: props.service.data.getValue(),
11
- }),
12
- constructed: ({ props, updateState, injector, element }) => {
13
- const disposables = [
14
- props.service.data.subscribe((data) => updateState({ data })),
9
+ render: ({ props, injector, useObservable }) => {
10
+ const { theme } = injector.getInstance(ThemeProviderService)
15
11
 
16
- injector.getInstance(ThemeProviderService).theme.subscribe((t) => {
17
- const el = element.querySelector('div') as HTMLDivElement
18
- el.style.color = t.text.secondary
19
- el.style.background = t.background.paper
20
- }),
21
- ]
12
+ const [currentData] = useObservable('dataUpdater', props.service.data)
22
13
 
23
- return () => disposables.forEach((d) => d.dispose())
24
- },
25
- render: ({ props, getState, injector }) => {
26
- const state = getState()
27
- const currentQuerySettings = props.service.querySettings.getValue()
28
- const currentPage = Math.ceil(currentQuerySettings.skip || 0) / (currentQuerySettings.top || 1)
29
- const currentEntriesPerPage = currentQuerySettings.top || Infinity
30
- const theme = injector.getInstance(ThemeProviderService).theme.getValue()
14
+ const [currentQuerySettings] = useObservable('querySettings', props.service.querySettings)
15
+
16
+ const top = currentQuerySettings.top || Infinity
17
+ const skip = currentQuerySettings.skip || 0
18
+ const currentPage = Math.ceil(skip) / (top || 1)
19
+ const currentEntriesPerPage = top
31
20
 
32
21
  return (
33
22
  <div
@@ -55,7 +44,7 @@ export const DataGridFooter = Shade<{ service: CollectionService<any> }, { data:
55
44
  }}
56
45
  >
57
46
  {[
58
- ...new Array(Math.ceil(state.data.count / (props.service.querySettings.getValue().top || Infinity))),
47
+ ...new Array(Math.ceil(currentData.count / (props.service.querySettings.getValue().top || Infinity))),
59
48
  ].map((_val, index) => (
60
49
  <option value={index.toString()} selected={currentPage === index}>
61
50
  {(index + 1).toString()}