cozy-bar 0.0.0-development

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/dist/cozy-bar.min.js +77 -0
  4. package/dist/cozy-bar.min.js.map +1 -0
  5. package/package.json +165 -0
  6. package/src/assets/icons/16/icon-storage-16.svg +3 -0
  7. package/src/assets/icons/24/icon-arrow-left.svg +3 -0
  8. package/src/assets/icons/32/icon-claudy.svg +1 -0
  9. package/src/assets/icons/apps/icon-collect.svg +25 -0
  10. package/src/assets/icons/apps/icon-drive.svg +17 -0
  11. package/src/assets/icons/apps/icon-market-soon.svg +25 -0
  12. package/src/assets/icons/apps/icon-photos.svg +19 -0
  13. package/src/assets/icons/apps/icon-soon.svg +21 -0
  14. package/src/assets/icons/apps/icon-store.svg +19 -0
  15. package/src/assets/icons/claudyActions/icon-bills.svg +6 -0
  16. package/src/assets/icons/claudyActions/icon-laptop.svg +7 -0
  17. package/src/assets/icons/claudyActions/icon-phone.svg +8 -0
  18. package/src/assets/icons/claudyActions/icon-question-mark.svg +6 -0
  19. package/src/assets/icons/comingsoon/icon-bank.svg +12 -0
  20. package/src/assets/icons/comingsoon/icon-sante.svg +12 -0
  21. package/src/assets/icons/comingsoon/icon-store.svg +6 -0
  22. package/src/assets/icons/icon-cozy.svg +3 -0
  23. package/src/assets/icons/icon-shield.svg +3 -0
  24. package/src/assets/icons/spinner.svg +4 -0
  25. package/src/assets/sprites/icon-apps.svg +1 -0
  26. package/src/assets/sprites/icon-cozy-home.svg +16 -0
  27. package/src/components/Apps/AppItem.jsx +117 -0
  28. package/src/components/Apps/AppItemPlaceholder.jsx +12 -0
  29. package/src/components/Apps/AppNavButtons.jsx +94 -0
  30. package/src/components/Apps/AppsContent.jsx +91 -0
  31. package/src/components/Apps/ButtonCozyHome.jsx +30 -0
  32. package/src/components/Apps/ButtonCozyHome.spec.jsx +53 -0
  33. package/src/components/Apps/IconCozyHome.jsx +38 -0
  34. package/src/components/Apps/index.jsx +72 -0
  35. package/src/components/Banner.jsx +41 -0
  36. package/src/components/Bar.jsx +295 -0
  37. package/src/components/Bar.spec.jsx +133 -0
  38. package/src/components/Claudy.jsx +81 -0
  39. package/src/components/ClaudyIcon.jsx +18 -0
  40. package/src/components/Drawer.jsx +227 -0
  41. package/src/components/Drawer.spec.jsx +98 -0
  42. package/src/components/SearchBar.jsx +358 -0
  43. package/src/components/Settings/SettingsContent.jsx +163 -0
  44. package/src/components/Settings/StorageData.jsx +29 -0
  45. package/src/components/Settings/helper.js +8 -0
  46. package/src/components/Settings/index.jsx +220 -0
  47. package/src/components/StorageIcon.jsx +16 -0
  48. package/src/components/SupportModal.jsx +59 -0
  49. package/src/components/__snapshots__/Bar.spec.jsx.snap +302 -0
  50. package/src/config/claudyActions.json +20 -0
  51. package/src/config/persistWhitelist.json +4 -0
  52. package/src/dom.js +80 -0
  53. package/src/index.jsx +242 -0
  54. package/src/index.spec.jsx +34 -0
  55. package/src/lib/api/helpers.js +13 -0
  56. package/src/lib/api/index.jsx +145 -0
  57. package/src/lib/exceptions.js +89 -0
  58. package/src/lib/expiringMemoize.js +13 -0
  59. package/src/lib/icon.js +77 -0
  60. package/src/lib/intents.js +16 -0
  61. package/src/lib/logger.js +11 -0
  62. package/src/lib/middlewares/appsI18n.js +57 -0
  63. package/src/lib/realtime.js +43 -0
  64. package/src/lib/reducers/apps.js +175 -0
  65. package/src/lib/reducers/apps.spec.js +59 -0
  66. package/src/lib/reducers/content.js +50 -0
  67. package/src/lib/reducers/context.js +86 -0
  68. package/src/lib/reducers/index.js +73 -0
  69. package/src/lib/reducers/locale.js +22 -0
  70. package/src/lib/reducers/settings.js +111 -0
  71. package/src/lib/reducers/theme.js +48 -0
  72. package/src/lib/reducers/unserializable.js +26 -0
  73. package/src/lib/stack-client.js +401 -0
  74. package/src/lib/stack.js +79 -0
  75. package/src/lib/store/index.js +44 -0
  76. package/src/locales/de.json +57 -0
  77. package/src/locales/en.json +57 -0
  78. package/src/locales/es.json +57 -0
  79. package/src/locales/fr.json +57 -0
  80. package/src/locales/it.json +57 -0
  81. package/src/locales/ja.json +57 -0
  82. package/src/locales/nl_NL.json +57 -0
  83. package/src/locales/pl.json +57 -0
  84. package/src/locales/ru.json +57 -0
  85. package/src/locales/sq.json +57 -0
  86. package/src/locales/zh_CN.json +57 -0
  87. package/src/proptypes/index.js +10 -0
  88. package/src/queries/index.js +16 -0
  89. package/src/styles/apps.css +248 -0
  90. package/src/styles/banner.css +64 -0
  91. package/src/styles/bar.css +106 -0
  92. package/src/styles/base.css +21 -0
  93. package/src/styles/claudy.css +98 -0
  94. package/src/styles/drawer.css +126 -0
  95. package/src/styles/index.styl +33 -0
  96. package/src/styles/indicators.css +58 -0
  97. package/src/styles/nav.css +81 -0
  98. package/src/styles/navigation_item.css +34 -0
  99. package/src/styles/searchbar.css +156 -0
  100. package/src/styles/settings.css +34 -0
  101. package/src/styles/storage.css +22 -0
  102. package/src/styles/supportModal.css +20 -0
  103. package/src/styles/theme.styl +25 -0
@@ -0,0 +1,295 @@
1
+ /* global __PIWIK_TRACKER_URL__ __PIWIK_SITEID__ __PIWIK_DIMENSION_ID_APP__ */
2
+
3
+ import React, { Component } from 'react'
4
+ import { connect } from 'react-redux'
5
+
6
+ import { translate } from 'cozy-ui/react/I18n'
7
+ import Icon from 'cozy-ui/react/Icon'
8
+ import {
9
+ shouldEnableTracking,
10
+ getTracker,
11
+ configureTracker
12
+ } from 'cozy-ui/react/helpers/tracker'
13
+ import { isMobileApp } from 'cozy-device-helper'
14
+
15
+ import { ButtonCozyHome } from 'components/Apps/ButtonCozyHome'
16
+ import Banner from 'components/Banner'
17
+ import Drawer from 'components/Drawer'
18
+ import Settings from 'components/Settings'
19
+ import Apps from 'components/Apps'
20
+ import SearchBar from 'components/SearchBar'
21
+ import Claudy from 'components/Claudy'
22
+ import SupportModal from 'components/SupportModal'
23
+ import {
24
+ getTheme,
25
+ hasFetched,
26
+ getContent,
27
+ isCurrentApp,
28
+ fetchApps,
29
+ fetchContext,
30
+ fetchSettingsData,
31
+ shouldEnableClaudy,
32
+ getWebviewContext
33
+ } from 'lib/reducers'
34
+
35
+ /* Generated with node_modules/.bin/svgr src/assets/sprites/icon-apps.svg */
36
+ function SvgIconApps(props) {
37
+ return (
38
+ <svg width={16} height={16} {...props}>
39
+ <path
40
+ d="M0 0h4v4H0V0zm0 6h4v4H0V6zm0 6h4v4H0v-4zM6 0h4v4H6V0zm0 6h4v4H6V6zm0 6h4v4H6v-4zm6-12h4v4h-4V0zm0 6h4v4h-4V6zm0 6h4v4h-4v-4z"
41
+ fillRule="evenodd"
42
+ />
43
+ </svg>
44
+ )
45
+ }
46
+
47
+ export class Bar extends Component {
48
+ constructor(props) {
49
+ super(props)
50
+ this.state = {
51
+ claudyFired: false, // true to fire claudy (used by the drawer)
52
+ claudyOpened: false,
53
+ drawerVisible: false,
54
+ usageTracker: null,
55
+ supportDisplayed: false,
56
+ searchBarEnabled: props.isDrive && !props.isPublic && !isMobileApp()
57
+ }
58
+ this.fetchApps = this.fetchApps.bind(this)
59
+ this.fetchInitialData = this.fetchInitialData.bind(this)
60
+ this.handleTokenRefreshed = this.handleTokenRefreshed.bind(this)
61
+ }
62
+
63
+ componentDidMount() {
64
+ if (shouldEnableTracking()) {
65
+ this.initPiwikTracker()
66
+ }
67
+ this.fetchInitialData()
68
+
69
+ const cozyClient = this.props.cozyClient
70
+ cozyClient.on('tokenRefreshed', this.handleTokenRefreshed)
71
+ }
72
+
73
+ componentWillUnmount() {
74
+ const cozyClient = this.props.cozyClient
75
+ cozyClient.removeListener('tokenRefreshed', this.handleTokenRefreshed)
76
+ }
77
+
78
+ componentDidUpdate(prevProps, prevState) {
79
+ if (
80
+ !this.props.hasFetchedApps &&
81
+ this.state.drawerVisible &&
82
+ prevState.drawerVisible !== this.state.drawerVisible
83
+ ) {
84
+ this.fetchApps()
85
+ }
86
+ }
87
+
88
+ handleTokenRefreshed() {
89
+ this.fetchInitialData()
90
+ }
91
+
92
+ initPiwikTracker() {
93
+ const trackerInstance = getTracker(
94
+ __PIWIK_TRACKER_URL__,
95
+ __PIWIK_SITEID__,
96
+ false,
97
+ false
98
+ )
99
+ configureTracker({
100
+ appDimensionId: __PIWIK_DIMENSION_ID_APP__,
101
+ app: 'Cozy Bar',
102
+ heartbeat: 0
103
+ })
104
+ this.setState({ usageTracker: trackerInstance })
105
+ }
106
+
107
+ fetchApps() {
108
+ this.props.fetchApps()
109
+ }
110
+
111
+ fetchInitialData() {
112
+ if (this.props.isPublic) {
113
+ return
114
+ }
115
+ this.props.fetchContext()
116
+ this.props.fetchSettingsData(false)
117
+ this.fetchApps()
118
+ }
119
+
120
+ toggleDrawer = () => {
121
+ // don't allow to toggle the drawer if claudy opened or is opening
122
+ if (this.state.claudyOpened || this.state.claudyFired) return
123
+ const drawerVisible = !this.state.drawerVisible
124
+ // don't wait for transitionend if displaying
125
+ if (drawerVisible) this.props.onDrawer(drawerVisible)
126
+ this.setState({ drawerVisible })
127
+ }
128
+
129
+ toggleClaudy = (isFromDrawer = false) => {
130
+ if (!this.props.claudyEnabled) return
131
+ const { usageTracker, claudyOpened } = this.state
132
+ if (isFromDrawer && !claudyOpened) {
133
+ // if opened from drawer
134
+ // reset to toggle via the Claudy component
135
+ return this.setState({ claudyFired: true })
136
+ }
137
+ if (this.state.claudyFired) this.setState({ claudyFired: false })
138
+ if (usageTracker) {
139
+ usageTracker.push([
140
+ 'trackEvent',
141
+ 'Claudy',
142
+ claudyOpened ? 'close' : 'open',
143
+ 'claudy'
144
+ ])
145
+ }
146
+ this.setState({ claudyOpened: !claudyOpened })
147
+ }
148
+
149
+ toggleSupport = () => {
150
+ const { supportDisplayed } = this.state
151
+ this.setState({ supportDisplayed: !supportDisplayed })
152
+ }
153
+
154
+ renderCenter() {
155
+ const { appName, appNamePrefix, appSlug, iconPath, isPublic } = this.props
156
+ return (
157
+ <Apps
158
+ appName={appName}
159
+ appNamePrefix={appNamePrefix}
160
+ appSlug={appSlug}
161
+ iconPath={iconPath}
162
+ isPublic={isPublic}
163
+ />
164
+ )
165
+ }
166
+
167
+ renderLeft = () => {
168
+ const { t, isPublic, webviewContext } = this.props
169
+
170
+ if (webviewContext) {
171
+ return <ButtonCozyHome webviewContext={webviewContext} />
172
+ }
173
+
174
+ // data-tutorial attribute allows to be targeted in an application tutorial
175
+ return !isPublic ? (
176
+ <button
177
+ type="button"
178
+ className="coz-bar-btn coz-bar-burger"
179
+ onClick={this.toggleDrawer}
180
+ data-tutorial="apps-mobile"
181
+ >
182
+ <Icon icon={SvgIconApps} width={16} height={16} color="currentColor" />
183
+ <span className="coz-bar-hidden">{t('drawer')}</span>
184
+ </button>
185
+ ) : null
186
+ }
187
+
188
+ renderRight = () => {
189
+ const { isPublic } = this.props
190
+ return !isPublic ? (
191
+ <Settings
192
+ toggleSupport={this.toggleSupport}
193
+ onLogOut={this.props.onLogOut}
194
+ />
195
+ ) : null
196
+ }
197
+
198
+ render() {
199
+ const {
200
+ claudyFired,
201
+ claudyOpened,
202
+ drawerVisible,
203
+ searchBarEnabled,
204
+ supportDisplayed,
205
+ usageTracker
206
+ } = this.state
207
+
208
+ const {
209
+ theme,
210
+ themeOverrides,
211
+ barLeft,
212
+ barRight,
213
+ barCenter,
214
+ barSearch,
215
+ claudyEnabled,
216
+ onDrawer,
217
+ isPublic,
218
+ onLogOut,
219
+ userActionRequired
220
+ } = this.props
221
+
222
+ const {
223
+ primaryColor: pColor,
224
+ primaryContrastTextColor: pctColor
225
+ } = themeOverrides
226
+ const pStyle = pColor ? { '--cozBarThemePrimaryColor': pColor } : {}
227
+ const pctStyle = pctColor
228
+ ? { '--cozBarThemePrimaryContrastTextColor': pctColor }
229
+ : {}
230
+ const themeStyle = { ...pStyle, ...pctStyle }
231
+
232
+ return (
233
+ <div className={`coz-bar-wrapper coz-theme-${theme}`} style={themeStyle}>
234
+ <div id="cozy-bar-modal-dom-place" />
235
+ <div className="coz-bar-container">
236
+ {barLeft || this.renderLeft()}
237
+ {barCenter || this.renderCenter()}
238
+ <div className="u-flex-grow">
239
+ {barSearch || (searchBarEnabled ? <SearchBar /> : null)}
240
+ </div>
241
+ {barRight || this.renderRight()}
242
+ {!isPublic ? (
243
+ <Drawer
244
+ visible={drawerVisible}
245
+ onClose={this.toggleDrawer}
246
+ onClaudy={
247
+ (claudyEnabled && (() => this.toggleClaudy(true))) || false
248
+ }
249
+ isClaudyLoading={claudyFired}
250
+ drawerListener={() => onDrawer(drawerVisible)}
251
+ toggleSupport={this.toggleSupport}
252
+ onLogOut={onLogOut}
253
+ />
254
+ ) : null}
255
+ {claudyEnabled && (
256
+ <Claudy
257
+ usageTracker={usageTracker}
258
+ claudyFired={claudyFired}
259
+ onToggle={() => this.toggleClaudy(false)}
260
+ opened={claudyOpened}
261
+ />
262
+ )}
263
+ {supportDisplayed && <SupportModal onClose={this.toggleSupport} />}
264
+ </div>
265
+ {userActionRequired && <Banner {...userActionRequired} />}
266
+ </div>
267
+ )
268
+ }
269
+ }
270
+
271
+ export const mapStateToProps = state => ({
272
+ theme: getTheme(state).name,
273
+ themeOverrides: getTheme(state).overrides,
274
+ barLeft: getContent(state, 'left'),
275
+ barRight: getContent(state, 'right'),
276
+ barCenter: getContent(state, 'center'),
277
+ barSearch: getContent(state, 'search'),
278
+ isDrive: isCurrentApp(state, { slug: 'drive' }),
279
+ claudyEnabled: shouldEnableClaudy(state),
280
+ hasFetchedApps: hasFetched(state),
281
+ webviewContext: getWebviewContext(state)
282
+ })
283
+
284
+ export const mapDispatchToProps = dispatch => ({
285
+ fetchApps: () => dispatch(fetchApps()),
286
+ fetchContext: () => dispatch(fetchContext()),
287
+ fetchSettingsData: displayBusy => dispatch(fetchSettingsData(displayBusy))
288
+ })
289
+
290
+ export default translate()(
291
+ connect(
292
+ mapStateToProps,
293
+ mapDispatchToProps
294
+ )(Bar)
295
+ )
@@ -0,0 +1,133 @@
1
+ import React from 'react'
2
+ import { shallow } from 'enzyme'
3
+ import { isFlagshipApp, isMobileApp } from 'cozy-device-helper'
4
+ import toJson from 'enzyme-to-json'
5
+ import reducers from 'lib/reducers'
6
+ import CozyClient from 'cozy-client'
7
+
8
+ import { Bar, mapStateToProps, mapDispatchToProps } from './Bar'
9
+
10
+ jest.mock('cozy-device-helper', () => ({
11
+ ...require.requireActual('cozy-device-helper'),
12
+ isMobileApp: jest.fn(),
13
+ isFlagshipApp: jest.fn()
14
+ }))
15
+
16
+ describe('Bar', () => {
17
+ let props, client
18
+ beforeEach(() => {
19
+ jest.resetAllMocks()
20
+ jest.spyOn(Bar.prototype, 'fetchApps')
21
+ isMobileApp.mockReturnValue(false)
22
+ client = new CozyClient({})
23
+ props = {
24
+ fetchContext: jest.fn().mockResolvedValue({}),
25
+ fetchApps: jest.fn().mockResolvedValue([]),
26
+ fetchSettingsData: jest.fn().mockResolvedValue({}),
27
+ theme: 'default',
28
+ themeOverrides: {},
29
+ t: x => x,
30
+ cozyClient: client
31
+ }
32
+ })
33
+
34
+ afterEach(() => {
35
+ Bar.prototype.fetchApps.mockRestore()
36
+ isFlagshipApp.mockClear()
37
+ })
38
+
39
+ it('should fetch data when mounted', () => {
40
+ shallow(<Bar {...props} />)
41
+ expect(props.fetchContext).toHaveBeenCalled()
42
+ expect(props.fetchApps).toHaveBeenCalled()
43
+ expect(props.fetchSettingsData).toHaveBeenCalled()
44
+ expect(Bar.prototype.fetchApps).toHaveBeenCalled()
45
+ })
46
+
47
+ it('should not fetch data if public', () => {
48
+ shallow(<Bar {...props} isPublic={true} />)
49
+ expect(props.fetchContext).not.toHaveBeenCalled()
50
+ expect(props.fetchApps).not.toHaveBeenCalled()
51
+ expect(props.fetchSettingsData).not.toHaveBeenCalled()
52
+ expect(Bar.prototype.fetchApps).not.toHaveBeenCalled()
53
+ })
54
+
55
+ it('should not fetch apps if hasFetchedApps is true', () => {
56
+ const barWrapper = shallow(<Bar {...props} hasFetchedApps={true} />)
57
+ jest.resetAllMocks()
58
+ barWrapper.setState({ drawerVisible: true }) // to call componentdidUpdate
59
+ expect(props.fetchApps).not.toHaveBeenCalled()
60
+ expect(Bar.prototype.fetchApps).not.toHaveBeenCalled()
61
+ })
62
+
63
+ it('should re-fetch apps if apps are not fetched and drawer opens', () => {
64
+ const barWrapper = shallow(<Bar {...props} />)
65
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(1)
66
+ expect(props.fetchApps).toHaveBeenCalledTimes(1)
67
+ barWrapper.setState({ drawerVisible: true })
68
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
69
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
70
+ barWrapper.setState({ drawerVisible: true, usageTracker: {} })
71
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
72
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
73
+ })
74
+
75
+ it('should change theme', () => {
76
+ const barWrapper = shallow(<Bar {...props} theme="primary" />)
77
+ expect(toJson(barWrapper)).toMatchSnapshot()
78
+ })
79
+
80
+ it('should change allow theme overrides', () => {
81
+ const barWrapper = shallow(
82
+ <Bar
83
+ {...props}
84
+ theme="primary"
85
+ themeOverrides={{ primaryColor: 'red' }}
86
+ />
87
+ )
88
+ expect(toJson(barWrapper)).toMatchSnapshot()
89
+ })
90
+
91
+ it('should display the Searchbar', () => {
92
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={false} />)
93
+ expect(toJson(barWrapper)).toMatchSnapshot()
94
+ })
95
+
96
+ it('should not display searchbar if we are on mobile', () => {
97
+ isMobileApp.mockReturnValue(true)
98
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={false} />)
99
+ expect(toJson(barWrapper)).toMatchSnapshot()
100
+ })
101
+
102
+ it('should not display searchbar if we are not on Cozy Drive', () => {
103
+ isMobileApp.mockReturnValue(true)
104
+ const barWrapper = shallow(
105
+ <Bar {...props} isDrive={false} isPublic={false} />
106
+ )
107
+ expect(toJson(barWrapper)).toMatchSnapshot()
108
+ })
109
+
110
+ it('should not display searchbar if we are not on a public page', () => {
111
+ isMobileApp.mockReturnValue(true)
112
+ const barWrapper = shallow(<Bar {...props} isDrive isPublic={true} />)
113
+ expect(toJson(barWrapper)).toMatchSnapshot()
114
+ })
115
+
116
+ it('should have correct state props provided by the store with the initial state', () => {
117
+ const initialState = reducers(undefined, {})
118
+ expect(mapStateToProps(initialState)).toMatchSnapshot()
119
+ })
120
+
121
+ it('should have correct dispatch props provided by the store', () => {
122
+ expect(mapDispatchToProps(jest.fn())).toMatchSnapshot()
123
+ })
124
+
125
+ it('should call re-fetch data when token is refreshed', () => {
126
+ shallow(<Bar {...props} isDrive={false} isPublic={false} />)
127
+ client.emit('tokenRefreshed')
128
+ expect(props.fetchContext).toHaveBeenCalledTimes(2)
129
+ expect(props.fetchApps).toHaveBeenCalledTimes(2)
130
+ expect(props.fetchSettingsData).toHaveBeenCalledTimes(2)
131
+ expect(Bar.prototype.fetchApps).toHaveBeenCalledTimes(2)
132
+ })
133
+ })
@@ -0,0 +1,81 @@
1
+ import React, { Component } from 'react'
2
+ import { getIntents } from 'lib/stack'
3
+
4
+ class Claudy extends Component {
5
+ constructor(props, context) {
6
+ super(props)
7
+ this.store = context.barStore
8
+ this.state = {
9
+ isLoading: false,
10
+ isActive: false
11
+ }
12
+ this.intents = getIntents()
13
+ }
14
+
15
+ UNSAFE_componentWillReceiveProps(nextProps) {
16
+ if (nextProps.claudyFired) this.toggle()
17
+ }
18
+
19
+ componentWillReceiveProps(nextProps) {
20
+ return this.UNSAFE_componentWillReceiveProps(nextProps)
21
+ }
22
+
23
+ toggle = () => {
24
+ if (!this.props.opened && !this.intentWrapperRef.childNodes.length) {
25
+ this.setState({ isLoading: true })
26
+ // init Claudy intent
27
+ this.intents
28
+ .create('CLAUDY', 'io.cozy.settings', {
29
+ exposeIntentFrameRemoval: true
30
+ })
31
+ .start(this.intentWrapperRef, () => {
32
+ this.setState({ isLoading: false, isActive: true })
33
+ this.props.onToggle() // toggle claudy when the intent is loaded
34
+ })
35
+ .then(({ removeIntentIframe }) => {
36
+ // exposeFrameRemoval intent event
37
+ // remove the intent frame at the end of the menu closing transition
38
+ const closedListener = e => {
39
+ if (e.propertyName === 'transform') {
40
+ removeIntentIframe()
41
+ this.setState({ isActive: false })
42
+ e.target.removeEventListener('transitionend', closedListener)
43
+ }
44
+ }
45
+ this.intentWrapperRef.addEventListener(
46
+ 'transitionend',
47
+ closedListener,
48
+ false
49
+ )
50
+ this.props.onToggle()
51
+ })
52
+ } else {
53
+ this.setState({ isActive: !this.state.isActive })
54
+ this.props.onToggle()
55
+ }
56
+ }
57
+
58
+ render() {
59
+ const { opened } = this.props
60
+ const { isLoading, isActive } = this.state
61
+ return (
62
+ <div className={`coz-claudy ${opened ? 'coz-claudy--opened' : ''}`}>
63
+ <button
64
+ type="button"
65
+ className="coz-claudy-icon coz-bar-hide-sm"
66
+ data-claudy-opened={isActive}
67
+ data-claudy-loading={isLoading}
68
+ onClick={this.toggle}
69
+ />
70
+ <div
71
+ className="coz-claudy-intent-wrapper"
72
+ ref={wrapper => {
73
+ this.intentWrapperRef = wrapper
74
+ }}
75
+ />
76
+ </div>
77
+ )
78
+ }
79
+ }
80
+
81
+ export default Claudy
@@ -0,0 +1,18 @@
1
+ import React from 'react'
2
+
3
+ function SvgIconClaudy(props) {
4
+ return (
5
+ <svg viewBox="0 0 32 32" {...props}>
6
+ <path
7
+ d="M22 12h-7c-.6 0-1 .4-1 1s.4 1 1 1h7c.6 0 1-.4 1-1s-.4-1-1-1zm3-5H15c-.5 0-1 .4-1 1s.4 1 1 1h10c.5 0 1-.4 1-1s-.4-1-1-1z"
8
+ fill="none"
9
+ />
10
+ <path
11
+ fill="#FFF"
12
+ d="M31 1H9c-.6 0-1 .5-1 1v15c2.2 0 4.1 1.2 5.2 3H18v1.9c0 .6.4.7.8.4l3.3-2.3H31c.6 0 1-.5 1-1V2c0-.6-.4-1-1-1zm-9 13h-7c-.6 0-1-.4-1-1s.4-1 1-1h7c.6 0 1 .4 1 1s-.4 1-1 1zm3-5H15c-.6 0-1-.4-1-1s.5-1 1-1h10c.6 0 1 .4 1 1s-.5 1-1 1zM10.4 26.7c0 .1-.7 1.3-2.4 1.3-1.8 0-2.4-1.2-2.4-1.3-.1-.2 0-.5.2-.7.2-.1.5 0 .7.2 0 0 .4.7 1.6.7 1.1 0 1.5-.7 1.6-.7.1-.2.4-.3.7-.2.1.2.2.5 0 .7M12 23c0-2.2-1.8-4-4-4s-4 1.8-4 4c-2.2 0-4 1.8-4 4s1.8 4 4 4h8c2.2 0 4-1.8 4-4s-1.8-4-4-4"
13
+ />
14
+ </svg>
15
+ )
16
+ }
17
+
18
+ export default SvgIconClaudy