@workday/canvas-kit-docs 8.2.1 → 8.2.3
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.
|
@@ -15,51 +15,20 @@ concepts. We will cover:
|
|
|
15
15
|
|
|
16
16
|
## Models
|
|
17
17
|
|
|
18
|
-
A model is composed of state and events.
|
|
18
|
+
A model is composed of state and events. The shape of the model used by components looks like this:
|
|
19
19
|
|
|
20
20
|
```tsx
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
The shape of a model is as follows:
|
|
26
|
-
|
|
27
|
-
```tsx
|
|
28
|
-
interface Model<S extends State, E extends Events> {
|
|
29
|
-
state: S;
|
|
30
|
-
events: E;
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
The `@workday/canvas-kit-react/common` module exports a `Model` type for us.
|
|
35
|
-
|
|
36
|
-
Let's start by defining our state and events:
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
// useDisclosureModel.tsx
|
|
40
|
-
import React from 'react';
|
|
41
|
-
|
|
42
|
-
import {Model} from '@workday/canvas-kit-react/common';
|
|
43
|
-
|
|
44
|
-
export type DisclosureState = {
|
|
45
|
-
visible: boolean;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type DisclosureEvents = {
|
|
49
|
-
show(data?: {}): void;
|
|
50
|
-
hide(data?: {}): void;
|
|
21
|
+
type Model = {
|
|
22
|
+
state: Record<string, any>;
|
|
23
|
+
events: Record<string, (data?: any) => void>;
|
|
51
24
|
};
|
|
52
|
-
|
|
53
|
-
export type DisclosureModel = Model<DisclosureState, DisclosureEvents>;
|
|
54
25
|
```
|
|
55
26
|
|
|
56
|
-
|
|
27
|
+
Our model hook will take a config for `initialVisible` and return a model.
|
|
57
28
|
|
|
58
29
|
```tsx
|
|
59
|
-
// useDisclosureModel.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export type DisclosureConfig = {
|
|
30
|
+
// useDisclosureModel.ts
|
|
31
|
+
type DisclosureConfig = {
|
|
63
32
|
initialVisible?: boolean;
|
|
64
33
|
};
|
|
65
34
|
|
|
@@ -83,10 +52,10 @@ export const useDisclosureModel = (config: DisclosureConfig = {}) => {
|
|
|
83
52
|
};
|
|
84
53
|
```
|
|
85
54
|
|
|
86
|
-
|
|
87
|
-
|
|
55
|
+
The model has a single `visible` state property and `show` and `hide` events we can send to the
|
|
56
|
+
model. So far using the model might look like this:
|
|
88
57
|
|
|
89
|
-
```
|
|
58
|
+
```jsx
|
|
90
59
|
const Test = () => {
|
|
91
60
|
const model = useDisclosureModel();
|
|
92
61
|
|
|
@@ -114,56 +83,56 @@ You can find a working example here: https://codesandbox.io/s/basic-disclosure-m
|
|
|
114
83
|
It would be nice to add guards and callbacks to our events. Let's add configuration to our model:
|
|
115
84
|
|
|
116
85
|
```tsx
|
|
117
|
-
|
|
86
|
+
type DisclosureConfig = {
|
|
118
87
|
initialVisible?: boolean;
|
|
119
88
|
// guards
|
|
120
|
-
shouldShow?(
|
|
121
|
-
shouldHide?(
|
|
89
|
+
shouldShow?(data: void, state: DisclosureState): boolean;
|
|
90
|
+
shouldHide?(data: void, state: DisclosureState): boolean;
|
|
122
91
|
// callbacks
|
|
123
|
-
onShow?(
|
|
124
|
-
onHide?(
|
|
92
|
+
onShow?(data: void, prevState: DisclosureState): void;
|
|
93
|
+
onHide?(data: void, prevState: DisclosureState): void;
|
|
125
94
|
};
|
|
126
95
|
```
|
|
127
96
|
|
|
128
97
|
We'll also have to add the runtime of the guards and actions:
|
|
129
98
|
|
|
130
99
|
```tsx
|
|
131
|
-
const events
|
|
132
|
-
show(
|
|
133
|
-
if (config.shouldShow?.(
|
|
100
|
+
const events = {
|
|
101
|
+
show() {
|
|
102
|
+
if (config.shouldShow?.(undefined, state) === false) {
|
|
134
103
|
return;
|
|
135
104
|
}
|
|
136
105
|
setVisible(true);
|
|
137
|
-
config.onShow?.(
|
|
106
|
+
config.onShow?.(undefined, state);
|
|
138
107
|
},
|
|
139
|
-
hide(
|
|
140
|
-
if (config.shouldHide?.(
|
|
108
|
+
hide() {
|
|
109
|
+
if (config.shouldHide?.(undefined, state) === false) {
|
|
141
110
|
return;
|
|
142
111
|
}
|
|
143
112
|
setVisible(false);
|
|
144
|
-
config.onHide?.(
|
|
113
|
+
config.onHide?.(undefined, state);
|
|
145
114
|
},
|
|
146
115
|
};
|
|
147
116
|
```
|
|
148
117
|
|
|
149
118
|
Now we should be able to configure the model via the guards and do something in the callbacks:
|
|
150
119
|
|
|
151
|
-
```
|
|
120
|
+
```jsx
|
|
152
121
|
const Test = () => {
|
|
153
122
|
const [should, setShould] = React.useState(true);
|
|
154
123
|
const model = useDisclosureModel({
|
|
155
|
-
shouldShow(
|
|
124
|
+
shouldShow(data, state) {
|
|
156
125
|
console.log('shouldShow', data, state, should);
|
|
157
126
|
return should;
|
|
158
127
|
},
|
|
159
|
-
shouldHide(
|
|
128
|
+
shouldHide(data, state) {
|
|
160
129
|
console.log('shouldHide', data, state, should);
|
|
161
130
|
return should;
|
|
162
131
|
},
|
|
163
|
-
onShow(
|
|
132
|
+
onShow(data, prevState) {
|
|
164
133
|
console.log('onShow', data, prevState);
|
|
165
134
|
},
|
|
166
|
-
onHide(
|
|
135
|
+
onHide(data, prevState) {
|
|
167
136
|
console.log('onHide', data, prevState);
|
|
168
137
|
},
|
|
169
138
|
});
|
|
@@ -206,71 +175,45 @@ You can see it in action here: https://codesandbox.io/s/basic-configurable-discl
|
|
|
206
175
|
That's a lot of extra boilerplate code for actions and callbacks. Our events don't have any data,
|
|
207
176
|
but if they did, we'd have to keep the event + guard and callback data types in sync. We are also
|
|
208
177
|
creating the `events` object every render. We could use React refs and `React.useMemo` to decrease
|
|
209
|
-
extra object creation. Luckily the common module has `
|
|
210
|
-
|
|
178
|
+
extra object creation. Luckily, the common module has the `createModelHook` factory function to help
|
|
179
|
+
us reduce boilerplate and reduce the possibility of making mistakes.
|
|
211
180
|
|
|
212
|
-
|
|
213
|
-
|
|
181
|
+
`createModelHook` creates a model and infers the config, state, and events. The callbacks and guard
|
|
182
|
+
types will automatically be inferred.
|
|
214
183
|
|
|
215
184
|
```tsx
|
|
216
|
-
|
|
185
|
+
// useDisclosureModel.ts
|
|
186
|
+
import {createModelHook} from '@workday/canvas-kit-react/common';
|
|
217
187
|
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
shouldHide: 'hide',
|
|
222
|
-
},
|
|
223
|
-
callbacks: {
|
|
224
|
-
onShow: 'show',
|
|
225
|
-
onHide: 'hide',
|
|
188
|
+
export const useDisclosureModel = createModelHook({
|
|
189
|
+
defaultConfig: {
|
|
190
|
+
initialVisible: false,
|
|
226
191
|
},
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
This part is a little weird: `createEventMap<DisclosureEvents>()({`. The reason for this is a
|
|
231
|
-
Typescript issue: https://github.com/microsoft/TypeScript/issues/26242. The gist is a function with
|
|
232
|
-
generics requires the caller to specify none of the generics or all of them. In this case, the only
|
|
233
|
-
generic that cannot be inferred is the `DisclosureEvents`. Everything else can be inferred. The only
|
|
234
|
-
way to separate defined generics vs inferred generics is to have separate, chained functions.
|
|
235
|
-
|
|
236
|
-
We see that `createEventMap` takes two optional keys: `guards` and `callbacks`. The names of these
|
|
237
|
-
functions are arbitrary, but should follow the convention of `should*` for guards and `on*` for
|
|
238
|
-
callbacks. The event name that is passed in is type checked against the `DisclosureEvents`
|
|
239
|
-
interface.
|
|
240
|
-
|
|
241
|
-
Now that we have an event map, we'll need to use it for our `DisclosureConfig`:
|
|
242
|
-
|
|
243
|
-
```tsx
|
|
244
|
-
import {ToModelConfig} from '@workday/canvas-kit-react/common';
|
|
245
|
-
|
|
246
|
-
export type DisclosureConfig = {
|
|
247
|
-
initialVisible?: boolean;
|
|
248
|
-
} & Partial<ToModelConfig<DisclosureState, DisclosureEvents, typeof disclosureEventMap>>;
|
|
249
|
-
```
|
|
192
|
+
})(config => {
|
|
193
|
+
const [visible, setVisible] = React.useState(config.initialVisible || false);
|
|
250
194
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
195
|
+
const state = {
|
|
196
|
+
visible,
|
|
197
|
+
};
|
|
254
198
|
|
|
255
|
-
|
|
256
|
-
|
|
199
|
+
const events = {
|
|
200
|
+
show() {
|
|
201
|
+
setVisible(true);
|
|
202
|
+
},
|
|
203
|
+
hide() {
|
|
204
|
+
setVisible(false);
|
|
205
|
+
},
|
|
206
|
+
};
|
|
257
207
|
|
|
258
|
-
|
|
259
|
-
const events = useEventMap(disclosureEventMap, state, config, {
|
|
260
|
-
show(data) {
|
|
261
|
-
setVisible(true);
|
|
262
|
-
},
|
|
263
|
-
hide(data) {
|
|
264
|
-
setVisible(false);
|
|
265
|
-
},
|
|
208
|
+
return {state, events};
|
|
266
209
|
});
|
|
267
210
|
```
|
|
268
211
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Neat!
|
|
212
|
+
`createModelHook` takes a config object to determine the default config and the required config. We
|
|
213
|
+
only need default config. This function returns a function with a `config` object with all config
|
|
214
|
+
defaults applied. This is the body of the `useDisclosureModel` hook from earlier. Notice we don't
|
|
215
|
+
need to implement guards and callbacks directly inside our event implementations. `createModelHook`
|
|
216
|
+
will return an object that has that functionality built right in! Neat!
|
|
274
217
|
|
|
275
218
|
The full working implementation is here:
|
|
276
219
|
https://codesandbox.io/s/configurable-disclosure-model-3y5qh
|
|
@@ -303,14 +246,15 @@ import React from 'react';
|
|
|
303
246
|
|
|
304
247
|
import {DisclosureTarget} from './DisclosureTarget';
|
|
305
248
|
import {DisclosureContent} from './DisclosureContent';
|
|
249
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
306
250
|
|
|
307
|
-
|
|
251
|
+
type DisclosureConfig = typeof useDisclosureModel.TConfig;
|
|
308
252
|
|
|
309
253
|
export interface DisclosureProps extends DisclosureConfig {
|
|
310
254
|
children: React.ReactNode;
|
|
311
255
|
}
|
|
312
256
|
|
|
313
|
-
|
|
257
|
+
const DisclosureModelContext = useDisclosureModel.Context;
|
|
314
258
|
|
|
315
259
|
export const Disclosure = ({children, ...config}: DisclosureProps) => {
|
|
316
260
|
const model = useDisclosureModel(config);
|
|
@@ -324,14 +268,14 @@ Disclosure.Target = DisclosureTarget;
|
|
|
324
268
|
Disclosure.Content = DisclosureContent;
|
|
325
269
|
```
|
|
326
270
|
|
|
327
|
-
We can see that the `DisclosureProps` interface extends the
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
instance.
|
|
271
|
+
We can see that the `DisclosureProps` interface extends the config of `useDisclosureModel`.
|
|
272
|
+
`createModelHook` exposes a `TConfig` property to capture the config type. This allows us to pass
|
|
273
|
+
the model config directly to the `<Disclosure>` component. A user of this `<Disclosure>` component
|
|
274
|
+
might want to register a callback when the `show` event is called, for instance.
|
|
331
275
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
component API to remain clean for consumers of compound components.
|
|
276
|
+
The `createModelHook` creates a React Context that can be used by the `Disclosure` component to
|
|
277
|
+
expose the disclosure model to subcomponents without having to pass it via props. This allows our
|
|
278
|
+
compound component API to remain clean for consumers of compound components.
|
|
335
279
|
|
|
336
280
|
In this particular compound component, the container component doesn't have a real element.
|
|
337
281
|
Accessibility specifications have no `role` for this component, so an element is not required.
|
|
@@ -343,14 +287,16 @@ Let's go ahead and finish out our sub-components.
|
|
|
343
287
|
```tsx
|
|
344
288
|
// DisclosureTarget.tsx
|
|
345
289
|
import React from 'react';
|
|
346
|
-
import
|
|
290
|
+
import React from 'react';
|
|
291
|
+
|
|
292
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
347
293
|
|
|
348
294
|
export interface DisclosureTargetProps {
|
|
349
295
|
children: React.ReactNode;
|
|
350
296
|
}
|
|
351
297
|
|
|
352
298
|
export const DisclosureTarget = ({children}: DisclosureTargetProps) => {
|
|
353
|
-
const model = React.useContext(
|
|
299
|
+
const model = React.useContext(useDisclosureModel.Context);
|
|
354
300
|
|
|
355
301
|
return (
|
|
356
302
|
<button
|
|
@@ -376,14 +322,15 @@ event on the model.
|
|
|
376
322
|
```tsx
|
|
377
323
|
// DisclosureContent.tsx
|
|
378
324
|
import React from 'react';
|
|
379
|
-
|
|
325
|
+
|
|
326
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
380
327
|
|
|
381
328
|
export interface DisclosureContentProps {
|
|
382
329
|
children: React.ReactNode;
|
|
383
330
|
}
|
|
384
331
|
|
|
385
332
|
export const DisclosureContent = ({children}: DisclosureContentProps) => {
|
|
386
|
-
const model = React.useContext(
|
|
333
|
+
const model = React.useContext(useDisclosureModel.Context);
|
|
387
334
|
|
|
388
335
|
return <div hidden={model.state.visible ? undefined : true}>{children}</div>;
|
|
389
336
|
};
|
|
@@ -395,18 +342,23 @@ set a `hidden` attribute.
|
|
|
395
342
|
The working example can be found here:
|
|
396
343
|
https://codesandbox.io/s/configurable-disclosure-model-components-nvhtv
|
|
397
344
|
|
|
398
|
-
These components are not fully compliant yet. They do not support `ref`, `as`, or extra
|
|
399
|
-
HTML attributes.
|
|
400
|
-
|
|
401
|
-
|
|
345
|
+
These components are not fully compliant yet. They do not support `model`, `ref`, `as`, or extra
|
|
346
|
+
props as HTML attributes. Also, we have to use `typeof` to create types and a `DisclosureContext`
|
|
347
|
+
variable (capitalized for JSX). We also have to worry about the `model` prop. The boilerplate for
|
|
348
|
+
supporting all of this gets very complicated. For this reason, `createContainer` and
|
|
349
|
+
`createSubcomponent` were created to handle this boilerplate for you out of the box. Both functions
|
|
350
|
+
take a default `React.ElementType` which can be an element string like `div` or `button` or a
|
|
402
351
|
component like `Button`. It also takes a config object containing the following:
|
|
403
352
|
|
|
404
353
|
- `displayName`: This will be the name of the component when shown by the React Dev tools. By
|
|
405
354
|
convention, we make that name be the same as typed in a render function. For example
|
|
406
355
|
`Disclosure.Target` vs `DisclosureTarget`.
|
|
407
|
-
- `
|
|
408
|
-
|
|
409
|
-
|
|
356
|
+
- `modelHook`: This is the model hook used by the compound component (`useDisclosureModel` in our
|
|
357
|
+
case). This model hook is used to determine proper prop types and seamlessly handle the option
|
|
358
|
+
`model` prop. For `createContainer`, if a `model` is not passed, a model is created and added to
|
|
359
|
+
React Context. For `createSubcomponent`, if a `model` is not passed, the model comes from React
|
|
360
|
+
Context.
|
|
361
|
+
- `elemPropsHook`: This is the elemPropsHook that takes a model and elemProps and returns elemProps.
|
|
410
362
|
- `subComponents`: For container components. A list of sub components to add to the returned
|
|
411
363
|
component. For example, a sub component called `DisclosureTarget` will be added to the export of
|
|
412
364
|
`Disclosure` so that the user can import only `Disclosure` and use `Disclosure.Target`.
|
|
@@ -414,102 +366,94 @@ component like `Button`. It also takes a config object containing the following:
|
|
|
414
366
|
interfaces. `Disclosure.Target = DisclosureTarget` will caused a type error. This property allows
|
|
415
367
|
the `createComponent` factory function to infer the final interface of the returned component.
|
|
416
368
|
|
|
417
|
-
|
|
369
|
+
Finally, a generic function is returned that takes the component configuration. The first argument
|
|
370
|
+
is `elemProps` with `ref` and hook props already merged in with props handed to the component. The
|
|
371
|
+
model config props will already be filtered out. We'll worry about `elemPropsHook` later. The second
|
|
372
|
+
is an `Element` property. `Element` is the value passed to the Component's `as` prop. It will
|
|
373
|
+
default to the provided element. The last parameter is an optional `model` reference. Ideally, the
|
|
374
|
+
model is used in `elemPropsHook` and therefore not normally needed inside the render function.
|
|
375
|
+
|
|
376
|
+
Let's convert the Disclosure example to use the `createContainer` utility function to get this extra
|
|
418
377
|
functionality:
|
|
419
378
|
|
|
420
379
|
```tsx
|
|
421
380
|
// Disclosure.tsx
|
|
422
381
|
import React from 'react';
|
|
423
|
-
import {
|
|
382
|
+
import {createContainer} from '@workday/canvas-kit-react/common';
|
|
383
|
+
|
|
424
384
|
import {DisclosureTarget} from './DisclosureTarget';
|
|
425
385
|
import {DisclosureContent} from './DisclosureContent';
|
|
386
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
426
387
|
|
|
427
|
-
|
|
388
|
+
export interface DisclosureProps {}
|
|
428
389
|
|
|
429
|
-
export
|
|
430
|
-
children: React.ReactNode;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
export const DisclosureModelContext = React.createContext({} as DisclosureModel);
|
|
434
|
-
|
|
435
|
-
export const Disclosure = createComponent()({
|
|
390
|
+
export const Disclosure = createContainer()({
|
|
436
391
|
displayName: 'Disclosure',
|
|
437
|
-
|
|
438
|
-
const model = useDisclosureModel(config);
|
|
439
|
-
|
|
440
|
-
return (
|
|
441
|
-
<DisclosureModelContext.Provider value={model}>{children}</DisclosureModelContext.Provider>
|
|
442
|
-
);
|
|
443
|
-
},
|
|
392
|
+
modelHook: useDisclosureModel,
|
|
444
393
|
subComponents: {
|
|
445
394
|
Target: DisclosureTarget,
|
|
446
395
|
Content: DisclosureContent,
|
|
447
396
|
},
|
|
397
|
+
})<DisclosureProps>(({children}) => {
|
|
398
|
+
return <>{children}</>;
|
|
448
399
|
});
|
|
449
400
|
```
|
|
450
401
|
|
|
402
|
+
Notice we do not need to add `children` or `model` to our prop definition. `createContainer` is
|
|
403
|
+
adding those prop types for us. The `displayName` helps identify the component in React developer
|
|
404
|
+
tools. This is only needed by container components. The `subComponents` automatically adds a
|
|
405
|
+
`displayName` to subcomponents using the property key. For example, our `DisclosureTarget` will have
|
|
406
|
+
a `displayName` of `Disclosure.Target`. You can still provide a `displayName` to override this
|
|
407
|
+
naming convention.
|
|
408
|
+
|
|
451
409
|
```tsx
|
|
452
410
|
// DisclosureTarget.tsx
|
|
453
411
|
import React from 'react';
|
|
454
|
-
import {
|
|
455
|
-
import {DisclosureModelContext} from './Disclosure';
|
|
412
|
+
import {createSubcomponent} from '@workday/canvas-kit-react/common';
|
|
456
413
|
|
|
457
|
-
|
|
458
|
-
children: React.ReactNode;
|
|
459
|
-
}
|
|
414
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
460
415
|
|
|
461
|
-
export
|
|
462
|
-
displayName: 'Disclosure.Target',
|
|
463
|
-
Component: ({children, ...elemProps}: DisclosureTargetProps, ref, Element) => {
|
|
464
|
-
const model = React.useContext(DisclosureModelContext);
|
|
416
|
+
export interface DisclosureTargetProps {}
|
|
465
417
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
},
|
|
418
|
+
export const DisclosureTarget = createSubcomponent('button')({
|
|
419
|
+
modelHook: useDisclosureModel,
|
|
420
|
+
})<DisclosureTargetProps>((elemProps, Element, model) => {
|
|
421
|
+
return (
|
|
422
|
+
<Element
|
|
423
|
+
onClick={() => {
|
|
424
|
+
if (model.state.visible) {
|
|
425
|
+
model.events.hide();
|
|
426
|
+
} else {
|
|
427
|
+
model.events.show();
|
|
428
|
+
}
|
|
429
|
+
}}
|
|
430
|
+
{...elemProps}
|
|
431
|
+
/>
|
|
432
|
+
);
|
|
482
433
|
});
|
|
483
434
|
```
|
|
484
435
|
|
|
485
436
|
```tsx
|
|
486
437
|
// DisclosureContent.tsx
|
|
487
438
|
import React from 'react';
|
|
488
|
-
import {
|
|
489
|
-
import {DisclosureModelContext} from './Disclosure';
|
|
439
|
+
import {createSubcomponent} from '@workday/canvas-kit-react/common';
|
|
490
440
|
|
|
491
|
-
|
|
492
|
-
children: React.ReactNode;
|
|
493
|
-
}
|
|
441
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
494
442
|
|
|
495
|
-
export
|
|
496
|
-
displayName: 'Disclosure.Content',
|
|
497
|
-
Component: ({children, ...elemProps}: DisclosureContentProps, ref, Element) => {
|
|
498
|
-
const model = React.useContext(DisclosureModelContext);
|
|
443
|
+
export interface DisclosureContentProps {}
|
|
499
444
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
445
|
+
export const DisclosureContent = createSubcomponent('div')({
|
|
446
|
+
modelHook: useDisclosureModel,
|
|
447
|
+
})<DisclosureContentProps>(({children, ...elemProps}, Element, model) => {
|
|
448
|
+
return (
|
|
449
|
+
<Element hidden={model.state.visible ? undefined : true} {...elemProps}>
|
|
450
|
+
{children}
|
|
451
|
+
</Element>
|
|
452
|
+
);
|
|
506
453
|
});
|
|
507
454
|
```
|
|
508
455
|
|
|
509
|
-
The `
|
|
510
|
-
For example `Disclose.Target` instead of `DiscloseTarget`.
|
|
511
|
-
|
|
512
|
-
The `as` prop is being passed to the 3rd argument in the and we're calling it `Element`. The
|
|
456
|
+
The `as` prop is being passed to the second argument in the and we're calling it `Element`. The
|
|
513
457
|
variable is passed to JSX as `<Element>`. `Element` is capitalized because the JSX parser treats
|
|
514
458
|
capitalized elements as variables and lower case elements as strings:
|
|
515
459
|
|
|
@@ -530,8 +474,9 @@ render `as` as an element. If we were using Emotion's `styled` components, we'd
|
|
|
530
474
|
not. Use `<Element>` when styling should come from the passed in element and use
|
|
531
475
|
`<StyledElement as={Element}>` when the component handles styling.
|
|
532
476
|
|
|
533
|
-
`
|
|
534
|
-
prop for changing the underlying element,
|
|
477
|
+
`createContainer` and `createSubcomponent` return a component with a type interface that includes
|
|
478
|
+
ref forwarding, the `as` prop for changing the underlying element, the `model` prop, and additional
|
|
479
|
+
attributes/props the element type normally takes.
|
|
535
480
|
|
|
536
481
|
For example, we can now do the following:
|
|
537
482
|
|
|
@@ -559,7 +504,7 @@ common so let's make a new model and compose from it instead. We'll later use th
|
|
|
559
504
|
reusable behavioral hook.
|
|
560
505
|
|
|
561
506
|
```tsx
|
|
562
|
-
// useIDModel.
|
|
507
|
+
// useIDModel.ts
|
|
563
508
|
import {Model, useUniqueId} from '@workday/canvas-kit-react/common';
|
|
564
509
|
|
|
565
510
|
export type IDState = {
|
|
@@ -593,21 +538,19 @@ Also later we'll add behavioral hook that will require this model.
|
|
|
593
538
|
Let's update the `DisclosureModel` to compose the `IDModel`:
|
|
594
539
|
|
|
595
540
|
```tsx
|
|
596
|
-
// useDisclosureModel.
|
|
597
|
-
|
|
598
|
-
import {IDState, IDConfig, useIDModel} from './useIDModel';
|
|
541
|
+
// useDisclosureModel.ts
|
|
542
|
+
import React from 'react';
|
|
599
543
|
|
|
600
|
-
|
|
601
|
-
visible: boolean;
|
|
602
|
-
};
|
|
544
|
+
import {createModelHook} from '@workday/canvas-kit-react/common';
|
|
603
545
|
|
|
604
|
-
|
|
546
|
+
import {useIDModel} from './useIDModel';
|
|
605
547
|
|
|
606
|
-
export
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
548
|
+
export const useDisclosureModel = createModelHook({
|
|
549
|
+
defaultConfig: {
|
|
550
|
+
...useIDModel.defaultConfig,
|
|
551
|
+
initialVisible: false,
|
|
552
|
+
},
|
|
553
|
+
})(config => {
|
|
611
554
|
const [visible, setVisible] = React.useState(config.initialVisible || false);
|
|
612
555
|
const idModel = useIDModel(config);
|
|
613
556
|
|
|
@@ -616,10 +559,18 @@ export const useDisclosureModel = (config: DisclosureConfig = {}) => {
|
|
|
616
559
|
visible,
|
|
617
560
|
};
|
|
618
561
|
|
|
619
|
-
|
|
562
|
+
const events = {
|
|
563
|
+
...idModel.events,
|
|
564
|
+
show() {
|
|
565
|
+
setVisible(true);
|
|
566
|
+
},
|
|
567
|
+
hide() {
|
|
568
|
+
setVisible(false);
|
|
569
|
+
},
|
|
570
|
+
};
|
|
620
571
|
|
|
621
572
|
return {state, events};
|
|
622
|
-
};
|
|
573
|
+
});
|
|
623
574
|
```
|
|
624
575
|
|
|
625
576
|
We can now add `aria-controls` to `DisclosureTarget` and `id` to `DisclosureContent`. We'll also add
|
|
@@ -632,7 +583,6 @@ We can now add `aria-controls` to `DisclosureTarget` and `id` to `DisclosureCont
|
|
|
632
583
|
|
|
633
584
|
return (
|
|
634
585
|
<Element
|
|
635
|
-
ref={ref}
|
|
636
586
|
aria-controls={model.state.id}
|
|
637
587
|
aria-expanded={model.state.visible}
|
|
638
588
|
onClick={() => {
|
|
@@ -657,12 +607,7 @@ return (
|
|
|
657
607
|
// ...
|
|
658
608
|
|
|
659
609
|
return (
|
|
660
|
-
<Element
|
|
661
|
-
ref={ref}
|
|
662
|
-
id={model.state.id}
|
|
663
|
-
hidden={model.state.visible ? undefined : true}
|
|
664
|
-
{...elemProps}
|
|
665
|
-
>
|
|
610
|
+
<Element id={model.state.id} hidden={model.state.visible ? undefined : true} {...elemProps}>
|
|
666
611
|
{children}
|
|
667
612
|
</Element>
|
|
668
613
|
);
|
|
@@ -685,10 +630,14 @@ UI of a dropdown menu look very different!
|
|
|
685
630
|
We'll build a behavior hook for the `DisclosureTarget` component:
|
|
686
631
|
|
|
687
632
|
```tsx
|
|
688
|
-
// useExpandableControls.
|
|
689
|
-
import {
|
|
690
|
-
|
|
691
|
-
export const useExpandableControls = (
|
|
633
|
+
// useExpandableControls.ts
|
|
634
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
635
|
+
|
|
636
|
+
export const useExpandableControls = (
|
|
637
|
+
{state}: ReturnType<typeof useDisclosureModel>,
|
|
638
|
+
elemProps = {},
|
|
639
|
+
ref?: React.Ref<any>
|
|
640
|
+
) => {
|
|
692
641
|
return {
|
|
693
642
|
'aria-controls': state.id,
|
|
694
643
|
'aria-expanded': state.visible,
|
|
@@ -706,11 +655,15 @@ won't get into that here, but it is useful and works with `composeHooks` that is
|
|
|
706
655
|
`common` module. Let's refactor the above to use that function:
|
|
707
656
|
|
|
708
657
|
```tsx
|
|
709
|
-
// useExpandableControls.
|
|
658
|
+
// useExpandableControls.ts
|
|
710
659
|
import {mergeProps} from '@workday/canvas-kit-react/common';
|
|
711
|
-
import {
|
|
660
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
712
661
|
|
|
713
|
-
export const useExpandableControls = (
|
|
662
|
+
export const useExpandableControls = (
|
|
663
|
+
{state}: ReturnType<typeof useDisclosureModel>,
|
|
664
|
+
elemProps = {},
|
|
665
|
+
ref?: React.Ref<any>
|
|
666
|
+
) => {
|
|
714
667
|
return mergeProps(
|
|
715
668
|
{
|
|
716
669
|
'aria-controls': state.id,
|
|
@@ -724,73 +677,167 @@ export const useExpandableControls = ({state}: DisclosureModel, elemProps: {}) =
|
|
|
724
677
|
Even though the `useExpandableControls` did not use any special props that need special merging, it
|
|
725
678
|
is a good habit to use `mergeProps` anytime you define props.
|
|
726
679
|
|
|
727
|
-
|
|
680
|
+
This is still a lot of boilerplate. We need the return type of the model hook, we need to specify
|
|
681
|
+
that our hook can optionally accept `elemProps` and a `ref`, and we need to call `mergeProps`.
|
|
682
|
+
`createElemPropsHook` helps with a lot of this boilerplate:
|
|
728
683
|
|
|
729
684
|
```tsx
|
|
730
|
-
|
|
731
|
-
import {
|
|
685
|
+
import {createElemPropsHook} from '@workday/canvas-kit-react/common';
|
|
686
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
687
|
+
|
|
688
|
+
export const useExpandableControls = createElemPropsHook(useDisclosureModel)(({state}) => {
|
|
689
|
+
return {
|
|
690
|
+
'aria-controls': state.id,
|
|
691
|
+
'aria-expanded': state.visible,
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
`createElemPropsHook` takes the model hook and an elem props hook body as arguments. The hook
|
|
697
|
+
function body doesn't need to call `mergeProps` since `createElemPropsHook` takes care of that for
|
|
698
|
+
us. Our logic can focus only on the props we need to add to an element!
|
|
699
|
+
|
|
700
|
+
Now we have a reusable elemProps hook that can be composed into other hooks or used on its own.
|
|
701
|
+
"expandable controls" could be used on a select component, a popup component, or any other type of
|
|
702
|
+
disclosure target component. We don't add the `onClick` because how the disclosure is revealed
|
|
703
|
+
depends on the disclosure target type. In a `Select` component, that could be by clicking on the
|
|
704
|
+
target, or using the down arrow. On a `Tooltip` component, it could be revealed by a mouse hover or
|
|
705
|
+
focus event. Lets create a `useDisclosureTarget` elemProps hook that merges in an `onClick` with
|
|
706
|
+
`useExpandableControls`:
|
|
707
|
+
|
|
708
|
+
```tsx
|
|
709
|
+
// useDisclosureTarget.ts
|
|
710
|
+
import {createElemPropsHook, mergeProps} from '@workday/canvas-kit-react/common';
|
|
711
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
732
712
|
import {useExpandableControls} from './useExpandableControls';
|
|
733
713
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
const props =
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
714
|
+
export const useDisclosureTarget = createElemPropsHook(useDisclosureModel)(
|
|
715
|
+
(model, ref, elemProps) => {
|
|
716
|
+
const props = useExpandableControls(model, elemProps, ref);
|
|
717
|
+
|
|
718
|
+
return mergeProps(
|
|
719
|
+
{
|
|
720
|
+
onClick() {
|
|
721
|
+
if (model.state.visible) {
|
|
722
|
+
model.events.hide();
|
|
723
|
+
} else {
|
|
724
|
+
model.events.show();
|
|
725
|
+
}
|
|
726
|
+
},
|
|
727
|
+
},
|
|
728
|
+
props
|
|
729
|
+
);
|
|
730
|
+
}
|
|
747
731
|
);
|
|
732
|
+
```
|
|
748
733
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
734
|
+
Notice we still need to use `mergeProps` to compose the behavior of our two elemProps hooks?
|
|
735
|
+
`composeHooks` was created to handle this common composition use case. `composeHooks` takes two or
|
|
736
|
+
more elemProps hooks and returns a new hook with all props merged for us:
|
|
737
|
+
|
|
738
|
+
```tsx
|
|
739
|
+
// useDisclosureTarget.ts
|
|
740
|
+
import {createElemPropsHook, composeHooks} from '@workday/canvas-kit-react/common';
|
|
741
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
742
|
+
import {useExpandableControls} from './useExpandableControls';
|
|
743
|
+
|
|
744
|
+
export const useDisclosureTarget = composeHooks(
|
|
745
|
+
createElemPropsHook(useDisclosureModel)(model => {
|
|
746
|
+
return {
|
|
747
|
+
onClick() {
|
|
748
|
+
if (model.state.visible) {
|
|
749
|
+
model.events.hide();
|
|
750
|
+
} else {
|
|
751
|
+
model.events.show();
|
|
752
|
+
}
|
|
753
|
+
},
|
|
754
|
+
};
|
|
755
|
+
}),
|
|
756
|
+
useExpandableControls
|
|
753
757
|
);
|
|
754
|
-
// ...
|
|
755
758
|
```
|
|
756
759
|
|
|
757
|
-
We
|
|
758
|
-
`onClick` will be called in addition to the `onClick` we've defined here. Without this, if the user
|
|
759
|
-
passes an `onClick`, it will override ours and break functionality. Definitely not what we want!
|
|
760
|
+
We don't even need to declare `elemProps` or `ref` parameters if we don't use them!
|
|
760
761
|
|
|
761
|
-
|
|
762
|
-
element:
|
|
762
|
+
Now we can use the behavior hook in the `DiscloseTarget` component:
|
|
763
763
|
|
|
764
764
|
```tsx
|
|
765
|
-
//
|
|
766
|
-
import
|
|
767
|
-
import {
|
|
765
|
+
// DisclosureTarget.tsx
|
|
766
|
+
import React from 'react';
|
|
767
|
+
import {createSubcomponent} from '@workday/canvas-kit-react/common';
|
|
768
768
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
769
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
770
|
+
import {useDisclosureTarget} from './useDisclosureTarget';
|
|
771
|
+
|
|
772
|
+
export interface DisclosureTargetProps {}
|
|
773
|
+
|
|
774
|
+
export const DisclosureTarget = createSubcomponent('button')({
|
|
775
|
+
modelHook: useDisclosureModel,
|
|
776
|
+
})<DisclosureTargetProps>((elemProps, Element, model) => {
|
|
777
|
+
const props = useDisclosureTarget(model, elemProps);
|
|
778
|
+
return <Element {...props} />;
|
|
779
|
+
});
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
Note: We should never use `createElemPropsHook` or `composeHooks` inside a render function as that
|
|
783
|
+
would be slower. Always hoist the hook definition outside a render function.
|
|
784
|
+
|
|
785
|
+
It is very common to use an elemProps hook with a compound component, so `createContainer` and
|
|
786
|
+
`createSubcomponent` both take an `elemPropsHook` configuration option. This way we don't have to
|
|
787
|
+
worry about the `model` or using `mergeProps` in our component definition. Here's the final code.
|
|
788
|
+
|
|
789
|
+
```tsx
|
|
790
|
+
// DisclosureTarget.tsx
|
|
791
|
+
import React from 'react';
|
|
792
|
+
import {createSubcomponent} from '@workday/canvas-kit-react/common';
|
|
793
|
+
|
|
794
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
795
|
+
import {useDisclosureTarget} from './useDisclosureTarget';
|
|
796
|
+
|
|
797
|
+
export interface DisclosureTargetProps {}
|
|
798
|
+
|
|
799
|
+
export const DisclosureTarget = createSubcomponent('button')({
|
|
800
|
+
modelHook: useDisclosureModel,
|
|
801
|
+
elemPropsHook: useDisclosureTarget,
|
|
802
|
+
})<DisclosureTargetProps>((elemProps, Element) => {
|
|
803
|
+
return <Element {...elemProps} />;
|
|
804
|
+
});
|
|
805
|
+
```
|
|
806
|
+
|
|
807
|
+
We'll also make a `useDisclosureContent` behavior hook for the `hidden` attribute on the
|
|
808
|
+
`Disclosure.Content` element:
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
// useDisclosureContent.ts
|
|
812
|
+
import {createElemPropsHook} from '@workday/canvas-kit-react/common';
|
|
813
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
814
|
+
|
|
815
|
+
export const useDisclosureContent = createElemPropsHook(useDisclosureModel)(model => {
|
|
816
|
+
return {
|
|
817
|
+
id: model.state.id,
|
|
818
|
+
hidden: model.state.visible ? undefined : true,
|
|
819
|
+
};
|
|
820
|
+
});
|
|
777
821
|
```
|
|
778
822
|
|
|
779
823
|
The `Disclosure.Content` subcomponent can now be updated to use this hook:
|
|
780
824
|
|
|
781
825
|
```tsx
|
|
782
826
|
// DisclosureContent.tsx
|
|
783
|
-
import
|
|
784
|
-
|
|
827
|
+
import React from 'react';
|
|
828
|
+
import {createSubcomponent} from '@workday/canvas-kit-react/common';
|
|
785
829
|
|
|
786
|
-
|
|
787
|
-
|
|
830
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
831
|
+
import {useDisclosureContent} from './useDisclosureContent';
|
|
788
832
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
833
|
+
export interface DisclosureContentProps {}
|
|
834
|
+
|
|
835
|
+
export const DisclosureContent = createSubcomponent('div')({
|
|
836
|
+
modelHook: useDisclosureModel,
|
|
837
|
+
elemPropsHook: useDisclosureContent,
|
|
838
|
+
})<DisclosureContentProps>(({children, ...elemProps}, Element) => {
|
|
839
|
+
return <Element {...elemProps}>{children}</Element>;
|
|
840
|
+
});
|
|
794
841
|
```
|
|
795
842
|
|
|
796
843
|
The full code can be found here:
|
|
@@ -806,32 +853,22 @@ mouse and focus events.
|
|
|
806
853
|
Here's a tooltip model composing the disclosure model:
|
|
807
854
|
|
|
808
855
|
```tsx
|
|
809
|
-
// useTooltipModel.
|
|
810
|
-
import {
|
|
811
|
-
|
|
812
|
-
import {
|
|
813
|
-
DisclosureConfig,
|
|
814
|
-
DisclosureEvents,
|
|
815
|
-
DisclosureState,
|
|
816
|
-
useDisclosureModel,
|
|
817
|
-
} from './useDisclosureModel';
|
|
818
|
-
|
|
819
|
-
export type TooltipState = DisclosureState;
|
|
820
|
-
export type TooltipEvents = DisclosureEvents;
|
|
821
|
-
export type TooltipModel = Model<TooltipState, TooltipEvents>;
|
|
822
|
-
|
|
823
|
-
export type TooltipConfig = DisclosureConfig & {
|
|
824
|
-
initialVisible?: never; // tooltips never start showing
|
|
825
|
-
};
|
|
856
|
+
// useTooltipModel.ts
|
|
857
|
+
import {createModelHook} from '@workday/canvas-kit-react/common';
|
|
826
858
|
|
|
827
|
-
|
|
828
|
-
const disclosure = useDisclosureModel(config);
|
|
859
|
+
import {useDisclosureModel} from './useDisclosureModel';
|
|
829
860
|
|
|
830
|
-
|
|
831
|
-
|
|
861
|
+
const {
|
|
862
|
+
initialVisible, // tooltips are never initially visible, so remove the option
|
|
863
|
+
...defaultConfig
|
|
864
|
+
} = useDisclosureModel.defaultConfig;
|
|
832
865
|
|
|
833
|
-
|
|
834
|
-
|
|
866
|
+
export const useTooltipModel = createModelHook({
|
|
867
|
+
defaultConfig,
|
|
868
|
+
requiredConfig: useDisclosureModel.requiredConfig,
|
|
869
|
+
})(config => {
|
|
870
|
+
return useDisclosureModel(config);
|
|
871
|
+
});
|
|
835
872
|
```
|
|
836
873
|
|
|
837
874
|
Not much interesting is happening here. We're not adding additional state or events, but we're
|
|
@@ -851,29 +888,25 @@ The `Tooltip` container component looks almost exactly like the Disclosure compo
|
|
|
851
888
|
```tsx
|
|
852
889
|
// Tooltip.tsx
|
|
853
890
|
import React from 'react';
|
|
854
|
-
import {
|
|
891
|
+
import {createContainer} from '@workday/canvas-kit-react/common';
|
|
855
892
|
|
|
856
|
-
import {useTooltipModel
|
|
893
|
+
import {useTooltipModel} from './useTooltipModel';
|
|
857
894
|
import {TooltipTarget} from './TooltipTarget';
|
|
858
895
|
import {TooltipContent} from './TooltipContent';
|
|
859
896
|
|
|
860
|
-
export
|
|
861
|
-
|
|
862
|
-
export interface TooltipProps extends TooltipConfig {
|
|
863
|
-
children: React.ReactNode;
|
|
897
|
+
export interface TooltipProps {
|
|
898
|
+
children?: React.ReactNode;
|
|
864
899
|
}
|
|
865
900
|
|
|
866
|
-
export const Tooltip =
|
|
901
|
+
export const Tooltip = createContainer()({
|
|
867
902
|
displayName: 'Tooltip',
|
|
868
|
-
|
|
869
|
-
const model = useTooltipModel(config);
|
|
870
|
-
|
|
871
|
-
return <TooltipModelContext.Provider value={model}>{children}</TooltipModelContext.Provider>;
|
|
872
|
-
},
|
|
903
|
+
modelHook: useTooltipModel,
|
|
873
904
|
subComponents: {
|
|
874
905
|
Target: TooltipTarget,
|
|
875
906
|
Content: TooltipContent,
|
|
876
907
|
},
|
|
908
|
+
})(({children}: TooltipProps) => {
|
|
909
|
+
return <>{children}</>;
|
|
877
910
|
});
|
|
878
911
|
```
|
|
879
912
|
|
|
@@ -881,49 +914,40 @@ The `Tooltip.Target` component is similar to the `DisclosureTarget` component, b
|
|
|
881
914
|
behavior. The tooltip triggers on different events. Here's the code:
|
|
882
915
|
|
|
883
916
|
```tsx
|
|
917
|
+
// TooltipTarget.tsx
|
|
884
918
|
import React from 'react';
|
|
885
|
-
import {
|
|
919
|
+
import {createSubcomponent, createElemPropsHook} from '@workday/canvas-kit-react/common';
|
|
886
920
|
|
|
887
|
-
import {
|
|
888
|
-
import {TooltipModel} from './useTooltipModel';
|
|
921
|
+
import {useTooltipModel} from './useTooltipModel';
|
|
889
922
|
|
|
890
923
|
export interface TooltipTargetProps {
|
|
891
924
|
children: React.ReactNode;
|
|
892
925
|
}
|
|
893
926
|
|
|
894
|
-
export const useTooltipTarget = ({state, events}
|
|
895
|
-
return
|
|
896
|
-
{
|
|
897
|
-
|
|
898
|
-
events.show();
|
|
899
|
-
},
|
|
900
|
-
onBlur() {
|
|
901
|
-
events.hide();
|
|
902
|
-
},
|
|
903
|
-
onMouseEnter() {
|
|
904
|
-
events.show();
|
|
905
|
-
},
|
|
906
|
-
onMouseLeave() {
|
|
907
|
-
events.hide();
|
|
908
|
-
},
|
|
909
|
-
'aria-describedby': state.id,
|
|
927
|
+
export const useTooltipTarget = createElemPropsHook(useTooltipModel)(({state, events}) => {
|
|
928
|
+
return {
|
|
929
|
+
onFocus(event: any) {
|
|
930
|
+
events.show();
|
|
910
931
|
},
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
932
|
+
onBlur() {
|
|
933
|
+
events.hide();
|
|
934
|
+
},
|
|
935
|
+
onMouseEnter() {
|
|
936
|
+
events.show();
|
|
937
|
+
},
|
|
938
|
+
onMouseLeave() {
|
|
939
|
+
events.hide();
|
|
940
|
+
},
|
|
941
|
+
'aria-describedby': state.id,
|
|
942
|
+
};
|
|
943
|
+
});
|
|
914
944
|
|
|
915
|
-
export const TooltipTarget =
|
|
945
|
+
export const TooltipTarget = createSubcomponent('button')({
|
|
916
946
|
displayName: 'Tooltip.Target',
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
return (
|
|
922
|
-
<Element ref={ref} {...props}>
|
|
923
|
-
{children}
|
|
924
|
-
</Element>
|
|
925
|
-
);
|
|
926
|
-
},
|
|
947
|
+
modelHook: useTooltipModel,
|
|
948
|
+
elemPropsHook: useTooltipTarget,
|
|
949
|
+
})<TooltipTargetProps>(({children, ...elemProps}, Element) => {
|
|
950
|
+
return <Element {...elemProps}>{children}</Element>;
|
|
927
951
|
});
|
|
928
952
|
```
|
|
929
953
|
|
|
@@ -933,41 +957,39 @@ comes from the `IDModel`.
|
|
|
933
957
|
The `Tooltip.Content` component is similar to the `Disclosure.Content` component, except that it
|
|
934
958
|
uses a ReactDOM portal to ensure the content appears on top of other content. This example doesn't
|
|
935
959
|
include a positional library and instead hard-codes positional values. Notice we can reuse our
|
|
936
|
-
`
|
|
960
|
+
`useDisclosureContent` behavior hook in this component!
|
|
937
961
|
|
|
938
962
|
```tsx
|
|
939
963
|
import React from 'react';
|
|
940
964
|
import ReactDOM from 'react-dom';
|
|
941
|
-
import {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
export
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
{
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
);
|
|
965
|
+
import {
|
|
966
|
+
createSubcomponent,
|
|
967
|
+
createElemPropsHook,
|
|
968
|
+
composeHooks,
|
|
969
|
+
} from '@workday/canvas-kit-react/common';
|
|
970
|
+
|
|
971
|
+
import {useDisclosureContent} from './useDisclosureContent';
|
|
972
|
+
import {useTooltipModel} from './useTooltipModel';
|
|
973
|
+
|
|
974
|
+
export interface TooltipContentProps {}
|
|
975
|
+
|
|
976
|
+
const useTooltipContent = composeHooks(
|
|
977
|
+
createElemPropsHook(useTooltipModel)(model => {
|
|
978
|
+
return {
|
|
979
|
+
style: {position: 'absolute', left: 80, top: 10},
|
|
980
|
+
};
|
|
981
|
+
}),
|
|
982
|
+
useDisclosureContent
|
|
983
|
+
);
|
|
961
984
|
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
},
|
|
985
|
+
export const TooltipContent = createSubcomponent('div')({
|
|
986
|
+
modelHook: useTooltipModel,
|
|
987
|
+
elemPropsHook: useTooltipContent,
|
|
988
|
+
})<TooltipContentProps>(({children, ...elemProps}, Element, model) => {
|
|
989
|
+
return ReactDOM.createPortal(
|
|
990
|
+
model.state.id ? <Element {...elemProps}>{children}</Element> : null,
|
|
991
|
+
document.body
|
|
992
|
+
);
|
|
971
993
|
});
|
|
972
994
|
```
|
|
973
995
|
|
|
@@ -87,7 +87,7 @@ export default () => {
|
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
return (
|
|
90
|
-
<Box ref={ref}
|
|
90
|
+
<Box ref={ref} width={contWidth}>
|
|
91
91
|
<FormField label={'Container Size'}>
|
|
92
92
|
<Select onChange={handleChange} value={value}>
|
|
93
93
|
<SelectOption label="1024px" value="desktop" />
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workday/canvas-kit-docs",
|
|
3
|
-
"version": "8.2.
|
|
3
|
+
"version": "8.2.3",
|
|
4
4
|
"description": "Documentation components of Canvas Kit components",
|
|
5
5
|
"author": "Workday, Inc. (https://www.workday.com)",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@storybook/csf": "0.0.1",
|
|
45
|
-
"@workday/canvas-kit-react": "^8.2.
|
|
45
|
+
"@workday/canvas-kit-react": "^8.2.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"fs-extra": "^10.0.0",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"mkdirp": "^1.0.3",
|
|
51
51
|
"typescript": "4.1"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "1849e0062203592d9589a58cfd002c000975364e"
|
|
54
54
|
}
|