@stack-spot/citric-react 0.37.1 → 0.39.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.
- package/CHANGELOG.md +13 -0
- package/dist/citric.css +2844 -2844
- package/dist/components/Accordion.d.ts +1 -1
- package/dist/components/Accordion.js +1 -1
- package/dist/components/Alert.d.ts +1 -1
- package/dist/components/Alert.js +1 -1
- package/dist/components/AsyncContent.d.ts +1 -1
- package/dist/components/AsyncContent.js +1 -1
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +1 -1
- package/dist/components/AvatarGroup.d.ts +1 -1
- package/dist/components/AvatarGroup.js +1 -1
- package/dist/components/Badge.d.ts +1 -1
- package/dist/components/Badge.js +1 -1
- package/dist/components/Blockquote.d.ts +1 -1
- package/dist/components/Blockquote.js +1 -1
- package/dist/components/Breadcrumb.d.ts +1 -1
- package/dist/components/Breadcrumb.js +1 -1
- package/dist/components/Button.d.ts +1 -1
- package/dist/components/Button.js +1 -1
- package/dist/components/ButtonLink.d.ts +1 -1
- package/dist/components/ButtonLink.js +1 -1
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +1 -1
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/CheckboxGroup.d.ts +1 -1
- package/dist/components/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup.js +2 -2
- package/dist/components/CheckboxGroup.js.map +1 -1
- package/dist/components/Circle.d.ts +1 -1
- package/dist/components/Circle.js +1 -1
- package/dist/components/Divider.d.ts +1 -1
- package/dist/components/Divider.js +1 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorBoundary.js +1 -1
- package/dist/components/ErrorMessage.d.ts +1 -1
- package/dist/components/ErrorMessage.js +1 -1
- package/dist/components/FallbackBoundary.d.ts +1 -1
- package/dist/components/FallbackBoundary.js +1 -1
- package/dist/components/Favorite.d.ts +1 -1
- package/dist/components/Favorite.js +1 -1
- package/dist/components/FieldGroup.d.ts +1 -1
- package/dist/components/FieldGroup.js +1 -1
- package/dist/components/Form.d.ts +2 -2
- package/dist/components/Form.js +1 -1
- package/dist/components/FormGroup.d.ts +1 -1
- package/dist/components/FormGroup.js +1 -1
- package/dist/components/Icon.d.ts +1 -1
- package/dist/components/Icon.js +1 -1
- package/dist/components/IconBox.d.ts +3 -3
- package/dist/components/IconBox.js +1 -1
- package/dist/components/ImageBox.d.ts +3 -3
- package/dist/components/ImageBox.js +1 -1
- package/dist/components/ImageWithFallback.d.ts +1 -1
- package/dist/components/ImageWithFallback.js +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/LoadingPanel.d.ts +1 -1
- package/dist/components/LoadingPanel.js +1 -1
- package/dist/components/MenuOverlay/Menu.d.ts +1 -1
- package/dist/components/MenuOverlay/Menu.js +1 -1
- package/dist/components/MenuOverlay/index.d.ts +1 -1
- package/dist/components/MenuOverlay/index.js +1 -1
- package/dist/components/Overlay/index.d.ts +4 -1
- package/dist/components/Overlay/index.d.ts.map +1 -1
- package/dist/components/Overlay/index.js +4 -1
- package/dist/components/Overlay/index.js.map +1 -1
- package/dist/components/Pagination.d.ts +1 -1
- package/dist/components/Pagination.js +1 -1
- package/dist/components/ProgressBar.d.ts +1 -1
- package/dist/components/ProgressBar.js +1 -1
- package/dist/components/ProgressCircular.d.ts +1 -1
- package/dist/components/ProgressCircular.js +1 -1
- package/dist/components/RadioGroup.d.ts +1 -1
- package/dist/components/RadioGroup.js +1 -1
- package/dist/components/Rating.d.ts +1 -1
- package/dist/components/Rating.js +1 -1
- package/dist/components/Select/MultiSelect.d.ts +1 -1
- package/dist/components/Select/MultiSelect.js +1 -1
- package/dist/components/Select/RichSelect.d.ts +1 -1
- package/dist/components/Select/RichSelect.js +1 -1
- package/dist/components/Select/SimpleSelect.d.ts +1 -1
- package/dist/components/Select/SimpleSelect.js +1 -1
- package/dist/components/Select/index.d.ts +1 -1
- package/dist/components/Select/index.js +1 -1
- package/dist/components/SelectBox.d.ts +1 -1
- package/dist/components/SelectBox.js +1 -1
- package/dist/components/Skeleton.d.ts +1 -1
- package/dist/components/Skeleton.js +1 -1
- package/dist/components/Slider.d.ts +1 -1
- package/dist/components/Slider.js +1 -1
- package/dist/components/SmartTable.d.ts +1 -1
- package/dist/components/SmartTable.js +1 -1
- package/dist/components/Stepper.d.ts +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +1 -1
- package/dist/components/Tabs/index.d.ts +1 -1
- package/dist/components/Tabs/index.js +1 -1
- package/dist/components/Textarea.d.ts +1 -1
- package/dist/components/Textarea.js +1 -1
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +1 -1
- package/dist/context/CitricProvider.d.ts +1 -1
- package/dist/context/CitricProvider.js +1 -1
- package/dist/overlay.js +1 -1
- package/dist/theme.css +415 -415
- package/package.json +2 -1
- package/scripts/build-css.ts +49 -49
- package/src/components/Accordion.tsx +130 -130
- package/src/components/Alert.tsx +24 -24
- package/src/components/AsyncContent.tsx +70 -70
- package/src/components/Avatar.tsx +45 -45
- package/src/components/AvatarGroup.tsx +49 -49
- package/src/components/Badge.tsx +47 -47
- package/src/components/Blockquote.tsx +18 -18
- package/src/components/Breadcrumb.tsx +33 -33
- package/src/components/Button.tsx +105 -105
- package/src/components/ButtonLink.tsx +45 -45
- package/src/components/Card.tsx +68 -68
- package/src/components/Checkbox.tsx +51 -51
- package/src/components/CheckboxGroup.tsx +153 -152
- package/src/components/Circle.tsx +43 -43
- package/src/components/CitricComponent.ts +47 -47
- package/src/components/Divider.tsx +24 -24
- package/src/components/ErrorBoundary.tsx +75 -75
- package/src/components/ErrorMessage.tsx +11 -11
- package/src/components/FallbackBoundary.tsx +40 -40
- package/src/components/Favorite.tsx +57 -57
- package/src/components/FieldGroup.tsx +46 -46
- package/src/components/Form.tsx +36 -36
- package/src/components/FormGroup.tsx +57 -57
- package/src/components/Icon.tsx +35 -35
- package/src/components/IconBox.tsx +134 -134
- package/src/components/ImageBox.tsx +125 -125
- package/src/components/ImageWithFallback.tsx +65 -65
- package/src/components/Input.tsx +49 -49
- package/src/components/Link.tsx +55 -55
- package/src/components/LoadingPanel.tsx +8 -8
- package/src/components/MenuOverlay/Menu.tsx +158 -158
- package/src/components/MenuOverlay/context.ts +20 -20
- package/src/components/MenuOverlay/index.tsx +55 -55
- package/src/components/MenuOverlay/keyboard.ts +60 -60
- package/src/components/MenuOverlay/types.ts +171 -171
- package/src/components/Overlay/context.ts +10 -10
- package/src/components/Overlay/index.tsx +167 -164
- package/src/components/Overlay/types.ts +70 -70
- package/src/components/Pagination.tsx +133 -133
- package/src/components/ProgressBar.tsx +45 -45
- package/src/components/ProgressCircular.tsx +45 -45
- package/src/components/RadioGroup.tsx +146 -146
- package/src/components/Rating.tsx +98 -98
- package/src/components/Select/MultiSelect.tsx +217 -217
- package/src/components/Select/RichSelect.tsx +128 -128
- package/src/components/Select/SimpleSelect.tsx +73 -73
- package/src/components/Select/hooks.ts +133 -133
- package/src/components/Select/index.tsx +35 -35
- package/src/components/Select/types.ts +134 -134
- package/src/components/SelectBox.tsx +167 -167
- package/src/components/Skeleton.tsx +53 -53
- package/src/components/Slider.tsx +89 -89
- package/src/components/SmartTable.tsx +227 -227
- package/src/components/Stepper.tsx +163 -163
- package/src/components/Table.tsx +234 -234
- package/src/components/Tabs/TabController.ts +54 -54
- package/src/components/Tabs/index.tsx +87 -87
- package/src/components/Tabs/types.ts +54 -54
- package/src/components/Tabs/utils.ts +6 -6
- package/src/components/Text.ts +111 -111
- package/src/components/Textarea.tsx +27 -27
- package/src/components/Tooltip.tsx +72 -72
- package/src/components/layout.tsx +101 -101
- package/src/context/CitricContext.tsx +4 -4
- package/src/context/CitricProvider.tsx +14 -14
- package/src/context/hooks.ts +6 -6
- package/src/index.ts +58 -58
- package/src/overlay.ts +341 -341
- package/src/types.ts +216 -216
- package/src/utils/ValueController.ts +28 -28
- package/src/utils/acessibility.ts +92 -92
- package/src/utils/checkbox.ts +121 -121
- package/src/utils/css.ts +119 -119
- package/src/utils/options.ts +9 -9
- package/src/utils/radio.ts +93 -93
- package/src/utils/react.ts +6 -6
- package/tsconfig.json +10 -10
|
@@ -1,125 +1,125 @@
|
|
|
1
|
-
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
-
import { useCitricController } from '../context/hooks'
|
|
3
|
-
import { HTMLTag, WithColorPalette, WithColorScheme } from '../types'
|
|
4
|
-
import { withRef } from '../utils/react'
|
|
5
|
-
import { asCitricProps, CitricComponent } from './CitricComponent'
|
|
6
|
-
|
|
7
|
-
type ImageBoxTag = 'a' | 'button' | 'span' | 'div'
|
|
8
|
-
|
|
9
|
-
export interface BaseImageBoxProps<T extends ImageBoxTag> extends WithColorPalette, WithColorScheme {
|
|
10
|
-
/**
|
|
11
|
-
* The HTML element to render.
|
|
12
|
-
*
|
|
13
|
-
* @default 'div'
|
|
14
|
-
*/
|
|
15
|
-
tag?: T,
|
|
16
|
-
/**
|
|
17
|
-
* The box appearance.
|
|
18
|
-
*
|
|
19
|
-
* @default 'circle'
|
|
20
|
-
*/
|
|
21
|
-
appearance?: 'circle' | 'square',
|
|
22
|
-
/**
|
|
23
|
-
* Size of the box.
|
|
24
|
-
*
|
|
25
|
-
* - xs: 20px;
|
|
26
|
-
* - sm: 24px;
|
|
27
|
-
* - md: 32px;
|
|
28
|
-
* - lg: 40px.
|
|
29
|
-
*
|
|
30
|
-
* @default 'sm'
|
|
31
|
-
*/
|
|
32
|
-
size?: 'xs' | 'sm' | 'md' | 'lg',
|
|
33
|
-
/**
|
|
34
|
-
* Animated text to show on when the button/link is clicked. This is only valid for buttons and anchors.
|
|
35
|
-
*/
|
|
36
|
-
feedback?: string,
|
|
37
|
-
/**
|
|
38
|
-
* Only valid if `tag` is "button" or "a".
|
|
39
|
-
*
|
|
40
|
-
* Metadata for the general onClick event, set by the CitricController. Useful for creating analytics data.
|
|
41
|
-
*
|
|
42
|
-
* This only takes effect if there's a CitricController in React's context. The value of `metadata` is passed to the function
|
|
43
|
-
* `onClickButton/onClickLink` of the controller.
|
|
44
|
-
*
|
|
45
|
-
* @default false
|
|
46
|
-
*/
|
|
47
|
-
metadata?: any,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export type ImageBoxProps<T extends ImageBoxTag> = HTMLTag[T] & BaseImageBoxProps<T>
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Renders a wrapper for its child (normally an image). The image will be resized and cropped to fit the container. The image is not cropped
|
|
54
|
-
* if the property "feedback" is set.
|
|
55
|
-
*
|
|
56
|
-
* Hover and focus effects are applied if the ImageBox is focusable.
|
|
57
|
-
*
|
|
58
|
-
* This works exactly like an IconBox, but instead of an Icon, it can contain anything.
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```
|
|
62
|
-
* <ImageBox><img src="https://images.com/myimage.png" /></ImageBox>
|
|
63
|
-
* ```
|
|
64
|
-
*/
|
|
65
|
-
export const ImageBox = withRef(
|
|
66
|
-
function ImageBox<T extends ImageBoxTag = 'div'>(
|
|
67
|
-
{ tag, appearance, size, className, metadata, onClick, feedback, ...props }: ImageBoxProps<T>,
|
|
68
|
-
) {
|
|
69
|
-
props['aria-label'] ||= props.title // accessibility
|
|
70
|
-
const citric = useCitricController()
|
|
71
|
-
|
|
72
|
-
function handleClick(e: React.MouseEvent<any>) {
|
|
73
|
-
onClick?.(e)
|
|
74
|
-
if (tag === 'button') citric?.onClickButton?.(e, metadata)
|
|
75
|
-
else if (tag === 'a') citric?.onClickLink?.(e, metadata)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const componentProps = {
|
|
79
|
-
tag: (tag || 'i') as any,
|
|
80
|
-
component: 'icon-box',
|
|
81
|
-
className: listToClass([appearance, size, className]),
|
|
82
|
-
'data-feedback': feedback || undefined,
|
|
83
|
-
onClick: ['button', 'a'].includes(tag ?? '') ? handleClick : onClick,
|
|
84
|
-
...props,
|
|
85
|
-
} as const
|
|
86
|
-
|
|
87
|
-
return tag === 'a' && citric?.renderLink
|
|
88
|
-
? citric.renderLink(asCitricProps(componentProps))
|
|
89
|
-
: <CitricComponent {...componentProps} />
|
|
90
|
-
},
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* A shortcut for `<ImageBox tag="button">`.
|
|
95
|
-
*
|
|
96
|
-
* Whenever a button is clicked, the function `onClickButton` of the nearest CitricController is called with the event and the value of the
|
|
97
|
-
* prop `metadata`.
|
|
98
|
-
*
|
|
99
|
-
* @example
|
|
100
|
-
* ```
|
|
101
|
-
* <ImageButton><img src="https://images.com/myimage.png" /></ImageButton>
|
|
102
|
-
* ```
|
|
103
|
-
*/
|
|
104
|
-
export const ImageButton = withRef(
|
|
105
|
-
function ImageButton(props: Omit<ImageBoxProps<'button'>, 'tag'>) {
|
|
106
|
-
return <ImageBox {...props} tag="button" type={props.type || 'button'} />
|
|
107
|
-
},
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* A shortcut for `<ImageBox tag="a">`.
|
|
112
|
-
*
|
|
113
|
-
* Whenever a link is clicked, the function `onClickLink` of the nearest CitricController is called with the event and the value of the
|
|
114
|
-
* prop `metadata`.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```
|
|
118
|
-
* <ImageLink href="#"><img src="https://images.com/myimage.png" /></ImageButton>
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
export const ImageLink = withRef(
|
|
122
|
-
function ImageLink(props: Omit<ImageBoxProps<'a'>, 'tag'>) {
|
|
123
|
-
return <ImageBox {...props} tag="a" />
|
|
124
|
-
},
|
|
125
|
-
)
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { useCitricController } from '../context/hooks'
|
|
3
|
+
import { HTMLTag, WithColorPalette, WithColorScheme } from '../types'
|
|
4
|
+
import { withRef } from '../utils/react'
|
|
5
|
+
import { asCitricProps, CitricComponent } from './CitricComponent'
|
|
6
|
+
|
|
7
|
+
type ImageBoxTag = 'a' | 'button' | 'span' | 'div'
|
|
8
|
+
|
|
9
|
+
export interface BaseImageBoxProps<T extends ImageBoxTag> extends WithColorPalette, WithColorScheme {
|
|
10
|
+
/**
|
|
11
|
+
* The HTML element to render.
|
|
12
|
+
*
|
|
13
|
+
* @default 'div'
|
|
14
|
+
*/
|
|
15
|
+
tag?: T,
|
|
16
|
+
/**
|
|
17
|
+
* The box appearance.
|
|
18
|
+
*
|
|
19
|
+
* @default 'circle'
|
|
20
|
+
*/
|
|
21
|
+
appearance?: 'circle' | 'square',
|
|
22
|
+
/**
|
|
23
|
+
* Size of the box.
|
|
24
|
+
*
|
|
25
|
+
* - xs: 20px;
|
|
26
|
+
* - sm: 24px;
|
|
27
|
+
* - md: 32px;
|
|
28
|
+
* - lg: 40px.
|
|
29
|
+
*
|
|
30
|
+
* @default 'sm'
|
|
31
|
+
*/
|
|
32
|
+
size?: 'xs' | 'sm' | 'md' | 'lg',
|
|
33
|
+
/**
|
|
34
|
+
* Animated text to show on when the button/link is clicked. This is only valid for buttons and anchors.
|
|
35
|
+
*/
|
|
36
|
+
feedback?: string,
|
|
37
|
+
/**
|
|
38
|
+
* Only valid if `tag` is "button" or "a".
|
|
39
|
+
*
|
|
40
|
+
* Metadata for the general onClick event, set by the CitricController. Useful for creating analytics data.
|
|
41
|
+
*
|
|
42
|
+
* This only takes effect if there's a CitricController in React's context. The value of `metadata` is passed to the function
|
|
43
|
+
* `onClickButton/onClickLink` of the controller.
|
|
44
|
+
*
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
metadata?: any,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type ImageBoxProps<T extends ImageBoxTag> = HTMLTag[T] & BaseImageBoxProps<T>
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Renders a wrapper for its child (normally an image). The image will be resized and cropped to fit the container. The image is not cropped
|
|
54
|
+
* if the property "feedback" is set.
|
|
55
|
+
*
|
|
56
|
+
* Hover and focus effects are applied if the ImageBox is focusable.
|
|
57
|
+
*
|
|
58
|
+
* This works exactly like an IconBox, but instead of an Icon, it can contain anything.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```
|
|
62
|
+
* <ImageBox><img src="https://images.com/myimage.png" /></ImageBox>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export const ImageBox = withRef(
|
|
66
|
+
function ImageBox<T extends ImageBoxTag = 'div'>(
|
|
67
|
+
{ tag, appearance, size, className, metadata, onClick, feedback, ...props }: ImageBoxProps<T>,
|
|
68
|
+
) {
|
|
69
|
+
props['aria-label'] ||= props.title // accessibility
|
|
70
|
+
const citric = useCitricController()
|
|
71
|
+
|
|
72
|
+
function handleClick(e: React.MouseEvent<any>) {
|
|
73
|
+
onClick?.(e)
|
|
74
|
+
if (tag === 'button') citric?.onClickButton?.(e, metadata)
|
|
75
|
+
else if (tag === 'a') citric?.onClickLink?.(e, metadata)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const componentProps = {
|
|
79
|
+
tag: (tag || 'i') as any,
|
|
80
|
+
component: 'icon-box',
|
|
81
|
+
className: listToClass([appearance, size, className]),
|
|
82
|
+
'data-feedback': feedback || undefined,
|
|
83
|
+
onClick: ['button', 'a'].includes(tag ?? '') ? handleClick : onClick,
|
|
84
|
+
...props,
|
|
85
|
+
} as const
|
|
86
|
+
|
|
87
|
+
return tag === 'a' && citric?.renderLink
|
|
88
|
+
? citric.renderLink(asCitricProps(componentProps))
|
|
89
|
+
: <CitricComponent {...componentProps} />
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A shortcut for `<ImageBox tag="button">`.
|
|
95
|
+
*
|
|
96
|
+
* Whenever a button is clicked, the function `onClickButton` of the nearest CitricController is called with the event and the value of the
|
|
97
|
+
* prop `metadata`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```
|
|
101
|
+
* <ImageButton><img src="https://images.com/myimage.png" /></ImageButton>
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export const ImageButton = withRef(
|
|
105
|
+
function ImageButton(props: Omit<ImageBoxProps<'button'>, 'tag'>) {
|
|
106
|
+
return <ImageBox {...props} tag="button" type={props.type || 'button'} />
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* A shortcut for `<ImageBox tag="a">`.
|
|
112
|
+
*
|
|
113
|
+
* Whenever a link is clicked, the function `onClickLink` of the nearest CitricController is called with the event and the value of the
|
|
114
|
+
* prop `metadata`.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```
|
|
118
|
+
* <ImageLink href="#"><img src="https://images.com/myimage.png" /></ImageButton>
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
export const ImageLink = withRef(
|
|
122
|
+
function ImageLink(props: Omit<ImageBoxProps<'a'>, 'tag'>) {
|
|
123
|
+
return <ImageBox {...props} tag="a" />
|
|
124
|
+
},
|
|
125
|
+
)
|
|
@@ -1,65 +1,65 @@
|
|
|
1
|
-
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
-
import { useEffect, useMemo, useState } from 'react'
|
|
3
|
-
import { WithStyleShortcuts } from '../types'
|
|
4
|
-
import { getStyleFromProps } from '../utils/css'
|
|
5
|
-
import { Skeleton } from './Skeleton'
|
|
6
|
-
|
|
7
|
-
export interface BaseImageWithFallbackProps extends Pick<WithStyleShortcuts, 'w' | 'h' | 'radius' | 'bg'> {
|
|
8
|
-
/**
|
|
9
|
-
* The react element to fallback to if the image can't be rendered.
|
|
10
|
-
*/
|
|
11
|
-
fallback: React.ReactNode,
|
|
12
|
-
/**
|
|
13
|
-
* Whether or not to show a skeleton when the image is still loading.
|
|
14
|
-
* @default false
|
|
15
|
-
*/
|
|
16
|
-
showLoading?: boolean,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export type ImageWithFallbackProps = JSX.IntrinsicElements['img'] & BaseImageWithFallbackProps
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Attempts to render an image. If it succeeds, the image is displayed, otherwise, a fallback is rendered.
|
|
23
|
-
*
|
|
24
|
-
* This is very useful if the value of "src" may be empty or if it may be broken. Instead of rendering nothing or the default browser error,
|
|
25
|
-
* it renders the fallback passed as parameter.
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* ```
|
|
29
|
-
* <ImageWithFallback src={item.image} fallback={<Icon icon="Agent" />} />
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export const ImageWithFallback = (
|
|
33
|
-
{ onLoad, onError, className, style: ogStyle, fallback, showLoading, w, h, radius, bg, ...props }: ImageWithFallbackProps,
|
|
34
|
-
) => {
|
|
35
|
-
const [state, setState] = useState<'loading' | 'ready' | 'error'>(props.src ? 'loading' : 'error')
|
|
36
|
-
const isLoading = state === 'loading' && showLoading
|
|
37
|
-
const style = useMemo(() => ({ ...getStyleFromProps({ w, h, radius, bg }), ...ogStyle }), [ogStyle, w, h, radius, bg])
|
|
38
|
-
|
|
39
|
-
function handleLoad(e: React.SyntheticEvent<HTMLImageElement, Event>) {
|
|
40
|
-
if (state === 'loading') setState('ready')
|
|
41
|
-
onLoad?.(e)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function handleError(e: React.SyntheticEvent<HTMLImageElement, Event>) {
|
|
45
|
-
setState('error')
|
|
46
|
-
onError?.(e)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
if (state !== 'loading') setState(props.src ? 'loading' : 'error')
|
|
51
|
-
}, [props.src])
|
|
52
|
-
|
|
53
|
-
if (state === 'error') return <div className={listToClass([className, 'center'])} style={style}>{fallback}</div>
|
|
54
|
-
return <>
|
|
55
|
-
{isLoading && <Skeleton className={className} style={style} />}
|
|
56
|
-
<img
|
|
57
|
-
onLoad={handleLoad}
|
|
58
|
-
onError={handleError}
|
|
59
|
-
style={{ ...style, ...(isLoading ? { pointerEvents: 'none', position: 'absolute', opacity: 0 } : {}) }}
|
|
60
|
-
aria-hidden={isLoading}
|
|
61
|
-
className={className}
|
|
62
|
-
{...props}
|
|
63
|
-
/>
|
|
64
|
-
</>
|
|
65
|
-
}
|
|
1
|
+
import { listToClass } from '@stack-spot/portal-theme'
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import { WithStyleShortcuts } from '../types'
|
|
4
|
+
import { getStyleFromProps } from '../utils/css'
|
|
5
|
+
import { Skeleton } from './Skeleton'
|
|
6
|
+
|
|
7
|
+
export interface BaseImageWithFallbackProps extends Pick<WithStyleShortcuts, 'w' | 'h' | 'radius' | 'bg'> {
|
|
8
|
+
/**
|
|
9
|
+
* The react element to fallback to if the image can't be rendered.
|
|
10
|
+
*/
|
|
11
|
+
fallback: React.ReactNode,
|
|
12
|
+
/**
|
|
13
|
+
* Whether or not to show a skeleton when the image is still loading.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
showLoading?: boolean,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ImageWithFallbackProps = JSX.IntrinsicElements['img'] & BaseImageWithFallbackProps
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Attempts to render an image. If it succeeds, the image is displayed, otherwise, a fallback is rendered.
|
|
23
|
+
*
|
|
24
|
+
* This is very useful if the value of "src" may be empty or if it may be broken. Instead of rendering nothing or the default browser error,
|
|
25
|
+
* it renders the fallback passed as parameter.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```
|
|
29
|
+
* <ImageWithFallback src={item.image} fallback={<Icon icon="Agent" />} />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const ImageWithFallback = (
|
|
33
|
+
{ onLoad, onError, className, style: ogStyle, fallback, showLoading, w, h, radius, bg, ...props }: ImageWithFallbackProps,
|
|
34
|
+
) => {
|
|
35
|
+
const [state, setState] = useState<'loading' | 'ready' | 'error'>(props.src ? 'loading' : 'error')
|
|
36
|
+
const isLoading = state === 'loading' && showLoading
|
|
37
|
+
const style = useMemo(() => ({ ...getStyleFromProps({ w, h, radius, bg }), ...ogStyle }), [ogStyle, w, h, radius, bg])
|
|
38
|
+
|
|
39
|
+
function handleLoad(e: React.SyntheticEvent<HTMLImageElement, Event>) {
|
|
40
|
+
if (state === 'loading') setState('ready')
|
|
41
|
+
onLoad?.(e)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function handleError(e: React.SyntheticEvent<HTMLImageElement, Event>) {
|
|
45
|
+
setState('error')
|
|
46
|
+
onError?.(e)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (state !== 'loading') setState(props.src ? 'loading' : 'error')
|
|
51
|
+
}, [props.src])
|
|
52
|
+
|
|
53
|
+
if (state === 'error') return <div className={listToClass([className, 'center'])} style={style}>{fallback}</div>
|
|
54
|
+
return <>
|
|
55
|
+
{isLoading && <Skeleton className={className} style={style} />}
|
|
56
|
+
<img
|
|
57
|
+
onLoad={handleLoad}
|
|
58
|
+
onError={handleError}
|
|
59
|
+
style={{ ...style, ...(isLoading ? { pointerEvents: 'none', position: 'absolute', opacity: 0 } : {}) }}
|
|
60
|
+
aria-hidden={isLoading}
|
|
61
|
+
className={className}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
</>
|
|
65
|
+
}
|
package/src/components/Input.tsx
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import { ControlledInput, WithColorScheme } from '../types'
|
|
2
|
-
import { withRef } from '../utils/react'
|
|
3
|
-
import { CitricComponent } from './CitricComponent'
|
|
4
|
-
|
|
5
|
-
export type SupportedInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' |
|
|
6
|
-
'text' | 'time' | 'url' | 'week'
|
|
7
|
-
|
|
8
|
-
export interface BaseInputProps<T extends SupportedInputType> extends WithColorScheme {
|
|
9
|
-
/**
|
|
10
|
-
* @default 'text'
|
|
11
|
-
*/
|
|
12
|
-
type?: T,
|
|
13
|
-
value?: T extends 'number' ? number : string,
|
|
14
|
-
onChange?: (value: T extends 'number' ? (number | undefined) : string) => void,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type InputProps<T extends SupportedInputType = 'text'> =
|
|
18
|
-
ControlledInput & BaseInputProps<T> & { type?: T }
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Renders a input.
|
|
22
|
-
*
|
|
23
|
-
* Attention: "onChange" doesn't receive an event, instead, it receives the new value of the input: a string, unless "type" is "number",
|
|
24
|
-
* in this case, it receives a number.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
*
|
|
28
|
-
* ```
|
|
29
|
-
* const [value, setValue] = useState('')
|
|
30
|
-
*
|
|
31
|
-
* return <Input value={value} onChange={setValue} />
|
|
32
|
-
* ```
|
|
33
|
-
*/
|
|
34
|
-
export const Input = withRef(
|
|
35
|
-
function Input<T extends SupportedInputType = 'text'>({ type, value, onChange, ...props }: InputProps<T>) {
|
|
36
|
-
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
37
|
-
if (!onChange) return
|
|
38
|
-
const newValue = e.target.value
|
|
39
|
-
if (type === 'number') {
|
|
40
|
-
const parsed = newValue ? parseFloat(newValue) : undefined
|
|
41
|
-
onChange(parsed as any)
|
|
42
|
-
} else {
|
|
43
|
-
onChange(newValue as any)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return <CitricComponent tag="input" component="input" type={type} value={value} onChange={handleChange} {...props} />
|
|
48
|
-
},
|
|
49
|
-
)
|
|
1
|
+
import { ControlledInput, WithColorScheme } from '../types'
|
|
2
|
+
import { withRef } from '../utils/react'
|
|
3
|
+
import { CitricComponent } from './CitricComponent'
|
|
4
|
+
|
|
5
|
+
export type SupportedInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'search' | 'tel' |
|
|
6
|
+
'text' | 'time' | 'url' | 'week'
|
|
7
|
+
|
|
8
|
+
export interface BaseInputProps<T extends SupportedInputType> extends WithColorScheme {
|
|
9
|
+
/**
|
|
10
|
+
* @default 'text'
|
|
11
|
+
*/
|
|
12
|
+
type?: T,
|
|
13
|
+
value?: T extends 'number' ? number : string,
|
|
14
|
+
onChange?: (value: T extends 'number' ? (number | undefined) : string) => void,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type InputProps<T extends SupportedInputType = 'text'> =
|
|
18
|
+
ControlledInput & BaseInputProps<T> & { type?: T }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Renders a input.
|
|
22
|
+
*
|
|
23
|
+
* Attention: "onChange" doesn't receive an event, instead, it receives the new value of the input: a string, unless "type" is "number",
|
|
24
|
+
* in this case, it receives a number.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
*
|
|
28
|
+
* ```
|
|
29
|
+
* const [value, setValue] = useState('')
|
|
30
|
+
*
|
|
31
|
+
* return <Input value={value} onChange={setValue} />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export const Input = withRef(
|
|
35
|
+
function Input<T extends SupportedInputType = 'text'>({ type, value, onChange, ...props }: InputProps<T>) {
|
|
36
|
+
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
37
|
+
if (!onChange) return
|
|
38
|
+
const newValue = e.target.value
|
|
39
|
+
if (type === 'number') {
|
|
40
|
+
const parsed = newValue ? parseFloat(newValue) : undefined
|
|
41
|
+
onChange(parsed as any)
|
|
42
|
+
} else {
|
|
43
|
+
onChange(newValue as any)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return <CitricComponent tag="input" component="input" type={type} value={value} onChange={handleChange} {...props} />
|
|
48
|
+
},
|
|
49
|
+
)
|
package/src/components/Link.tsx
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
import { useCitricController } from '../context/hooks'
|
|
2
|
-
import { TextAppearance, WithColor } from '../types'
|
|
3
|
-
import { applyColor, applyTextAppearance } from '../utils/css'
|
|
4
|
-
import { withRef } from '../utils/react'
|
|
5
|
-
import { asCitricProps, CitricComponent } from './CitricComponent'
|
|
6
|
-
|
|
7
|
-
export interface BaseLinkProps extends WithColor {
|
|
8
|
-
/**
|
|
9
|
-
* The text appearance (font and size).
|
|
10
|
-
* By default, it inherits from the parent element.
|
|
11
|
-
*/
|
|
12
|
-
appearance?: TextAppearance,
|
|
13
|
-
/**
|
|
14
|
-
* Metadata for the general onClick event, set by the CitricController. Useful for creating analytics data.
|
|
15
|
-
*
|
|
16
|
-
* This only takes effect if there's a CitricController in React's context. The value of `metadata` is passed to the function
|
|
17
|
-
* `onClickLink` of the controller.
|
|
18
|
-
*
|
|
19
|
-
* @default false
|
|
20
|
-
*/
|
|
21
|
-
metadata?: any,
|
|
22
|
-
/**
|
|
23
|
-
* Whether or not to underline the link.
|
|
24
|
-
*
|
|
25
|
-
* @default true
|
|
26
|
-
*/
|
|
27
|
-
underline?: boolean,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type LinkProps = React.JSX.IntrinsicElements['a'] & BaseLinkProps
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Renders an html anchor by default, the actual component to render may be set on a CitricController, through the function `renderLink`.
|
|
34
|
-
*
|
|
35
|
-
* Whenever a link is clicked, the function `onClickLink` of the nearest CitricController is called with the event and the value of the
|
|
36
|
-
* prop `metadata`.
|
|
37
|
-
*/
|
|
38
|
-
export const Link = withRef(
|
|
39
|
-
({ appearance, color, style, className, children, onClick, metadata, underline = true, ...props }: LinkProps) => {
|
|
40
|
-
const citric = useCitricController()
|
|
41
|
-
const linkProps = {
|
|
42
|
-
component: 'link',
|
|
43
|
-
style: applyColor({ ...style, textDecoration: underline ? undefined : 'none' }, color),
|
|
44
|
-
className: applyTextAppearance(className, appearance),
|
|
45
|
-
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
46
|
-
onClick?.(e)
|
|
47
|
-
citric?.onClickLink?.(e, metadata ?? false)
|
|
48
|
-
},
|
|
49
|
-
...props,
|
|
50
|
-
} as const
|
|
51
|
-
return citric?.renderLink
|
|
52
|
-
? citric.renderLink(asCitricProps({ ...linkProps, children }))
|
|
53
|
-
: <CitricComponent tag="a" {...linkProps}>{children}</CitricComponent>
|
|
54
|
-
},
|
|
55
|
-
)
|
|
1
|
+
import { useCitricController } from '../context/hooks'
|
|
2
|
+
import { TextAppearance, WithColor } from '../types'
|
|
3
|
+
import { applyColor, applyTextAppearance } from '../utils/css'
|
|
4
|
+
import { withRef } from '../utils/react'
|
|
5
|
+
import { asCitricProps, CitricComponent } from './CitricComponent'
|
|
6
|
+
|
|
7
|
+
export interface BaseLinkProps extends WithColor {
|
|
8
|
+
/**
|
|
9
|
+
* The text appearance (font and size).
|
|
10
|
+
* By default, it inherits from the parent element.
|
|
11
|
+
*/
|
|
12
|
+
appearance?: TextAppearance,
|
|
13
|
+
/**
|
|
14
|
+
* Metadata for the general onClick event, set by the CitricController. Useful for creating analytics data.
|
|
15
|
+
*
|
|
16
|
+
* This only takes effect if there's a CitricController in React's context. The value of `metadata` is passed to the function
|
|
17
|
+
* `onClickLink` of the controller.
|
|
18
|
+
*
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
metadata?: any,
|
|
22
|
+
/**
|
|
23
|
+
* Whether or not to underline the link.
|
|
24
|
+
*
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
underline?: boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type LinkProps = React.JSX.IntrinsicElements['a'] & BaseLinkProps
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders an html anchor by default, the actual component to render may be set on a CitricController, through the function `renderLink`.
|
|
34
|
+
*
|
|
35
|
+
* Whenever a link is clicked, the function `onClickLink` of the nearest CitricController is called with the event and the value of the
|
|
36
|
+
* prop `metadata`.
|
|
37
|
+
*/
|
|
38
|
+
export const Link = withRef(
|
|
39
|
+
({ appearance, color, style, className, children, onClick, metadata, underline = true, ...props }: LinkProps) => {
|
|
40
|
+
const citric = useCitricController()
|
|
41
|
+
const linkProps = {
|
|
42
|
+
component: 'link',
|
|
43
|
+
style: applyColor({ ...style, textDecoration: underline ? undefined : 'none' }, color),
|
|
44
|
+
className: applyTextAppearance(className, appearance),
|
|
45
|
+
onClick: (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
46
|
+
onClick?.(e)
|
|
47
|
+
citric?.onClickLink?.(e, metadata ?? false)
|
|
48
|
+
},
|
|
49
|
+
...props,
|
|
50
|
+
} as const
|
|
51
|
+
return citric?.renderLink
|
|
52
|
+
? citric.renderLink(asCitricProps({ ...linkProps, children }))
|
|
53
|
+
: <CitricComponent tag="a" {...linkProps}>{children}</CitricComponent>
|
|
54
|
+
},
|
|
55
|
+
)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Center } from './layout'
|
|
2
|
-
import { ProgressCircular } from './ProgressCircular'
|
|
3
|
-
|
|
4
|
-
export const LoadingPanel = () => (
|
|
5
|
-
<Center flex={1} p="80px" style={{ alignSelf: 'stretch' }} data-test-hint="loading">
|
|
6
|
-
<ProgressCircular />
|
|
7
|
-
</Center>
|
|
8
|
-
)
|
|
1
|
+
import { Center } from './layout'
|
|
2
|
+
import { ProgressCircular } from './ProgressCircular'
|
|
3
|
+
|
|
4
|
+
export const LoadingPanel = () => (
|
|
5
|
+
<Center flex={1} p="80px" style={{ alignSelf: 'stretch' }} data-test-hint="loading">
|
|
6
|
+
<ProgressCircular />
|
|
7
|
+
</Center>
|
|
8
|
+
)
|