@stack-spot/citric-react 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/citric.css +239 -1
  2. package/dist/components/Autocomplete.d.ts +370 -0
  3. package/dist/components/Autocomplete.d.ts.map +1 -0
  4. package/dist/components/Autocomplete.js +474 -0
  5. package/dist/components/Autocomplete.js.map +1 -0
  6. package/dist/components/CitricComponent.d.ts +1 -1
  7. package/dist/components/CitricComponent.d.ts.map +1 -1
  8. package/dist/components/IconBox.d.ts +4 -2
  9. package/dist/components/IconBox.d.ts.map +1 -1
  10. package/dist/components/IconBox.js.map +1 -1
  11. package/dist/components/ImageBox.d.ts +4 -2
  12. package/dist/components/ImageBox.d.ts.map +1 -1
  13. package/dist/components/ImageBox.js.map +1 -1
  14. package/dist/components/Overlay/index.d.ts +1 -1
  15. package/dist/components/Overlay/index.d.ts.map +1 -1
  16. package/dist/components/Overlay/index.js +24 -19
  17. package/dist/components/Overlay/index.js.map +1 -1
  18. package/dist/components/Select/RichSelect.d.ts +1 -1
  19. package/dist/components/Select/RichSelect.d.ts.map +1 -1
  20. package/dist/components/Select/RichSelect.js +2 -2
  21. package/dist/components/Select/RichSelect.js.map +1 -1
  22. package/dist/components/Select/SimpleSelect.d.ts +1 -1
  23. package/dist/components/Select/SimpleSelect.d.ts.map +1 -1
  24. package/dist/components/Select/SimpleSelect.js +2 -2
  25. package/dist/components/Select/SimpleSelect.js.map +1 -1
  26. package/dist/components/Select/types.d.ts +8 -0
  27. package/dist/components/Select/types.d.ts.map +1 -1
  28. package/dist/components/Tooltip.d.ts.map +1 -1
  29. package/dist/components/Tooltip.js +1 -1
  30. package/dist/components/Tooltip.js.map +1 -1
  31. package/dist/index.d.ts +1 -0
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +1 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/overlay.d.ts +12 -0
  36. package/dist/overlay.d.ts.map +1 -1
  37. package/dist/overlay.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/components/Autocomplete.tsx +1038 -0
  40. package/src/components/CitricComponent.ts +1 -1
  41. package/src/components/IconBox.tsx +8 -2
  42. package/src/components/ImageBox.tsx +8 -2
  43. package/src/components/Overlay/index.tsx +23 -17
  44. package/src/components/Select/RichSelect.tsx +4 -0
  45. package/src/components/Select/SimpleSelect.tsx +6 -0
  46. package/src/components/Select/types.ts +11 -0
  47. package/src/components/Tooltip.tsx +2 -1
  48. package/src/index.ts +1 -0
  49. package/src/overlay.ts +12 -0
@@ -5,7 +5,7 @@ export type CitricComponentName = 'alert' | 'avatar' | 'badge' | 'blockquote' |
5
5
  'checkbox-row' | 'divider' | 'field-group' | 'form-group' | 'form' | 'icon-box' | 'input' | 'link' | 'pagination' | 'progress-bar' |
6
6
  'progress-circular' | 'radio' | 'radio-row' | 'rating' | 'select' | 'select-box' | 'skeleton' | 'slider' | 'switch' | 'switch-row' |
7
7
  'table' | 'tabs' | 'accordion' | 'favorite' | 'textarea' | 'avatar-group' | 'labeled-slider' | 'rich-select' | 'tooltip' | 'menu' |
8
- 'circle' | 'multi-select'
8
+ 'circle' | 'multi-select' | 'autocomplete'
9
9
 
10
10
  interface BaseCitricProps extends WithColorScheme, WithColorPalette {
11
11
  component: CitricComponentName,
@@ -58,6 +58,12 @@ export interface BaseIconBoxProps<T extends IconBoxTag, G extends IconGroup> ext
58
58
  }
59
59
 
60
60
  export type IconBoxProps<T extends IconBoxTag, G extends IconGroup> = Omit<HTMLTag[T], 'children'> & BaseIconBoxProps<T, G>
61
+ export type IconButtonProps<G extends IconGroup> = (
62
+ Omit<React.JSX.IntrinsicElements['button'], 'children'> & Omit<BaseIconBoxProps<'button', G>, 'tag'>
63
+ )
64
+ export type IconLinkProps<G extends IconGroup> = (
65
+ Omit<React.JSX.IntrinsicElements['a'], 'children'> & Omit<BaseIconBoxProps<'a', G>, 'tag'>
66
+ )
61
67
 
62
68
  /**
63
69
  * Renders a wrapper for an icon. The icon must specified by the properties "icon" and "group", this component accepts no children.
@@ -111,7 +117,7 @@ export const IconBox = withRef(
111
117
  * ```
112
118
  */
113
119
  export const IconButton = withRef(
114
- function IconButton<G extends IconGroup = 'outline'>({ type, ...props }: Omit<IconBoxProps<'button', G>, 'tag'>) {
120
+ function IconButton<G extends IconGroup = 'outline'>({ type, ...props }: IconButtonProps<G>) {
115
121
  return <IconBox {...props} tag="button" type={type || 'button' } />
116
122
  },
117
123
  )
@@ -128,7 +134,7 @@ export const IconButton = withRef(
128
134
  * ```
129
135
  */
130
136
  export const IconLink = withRef(
131
- function IconLink<G extends IconGroup = 'outline'>(props: Omit<IconBoxProps<'a', G>, 'tag'>) {
137
+ function IconLink<G extends IconGroup = 'outline'>(props: IconLinkProps<G>) {
132
138
  return <IconBox {...props} tag="a" />
133
139
  },
134
140
  )
@@ -48,6 +48,12 @@ export interface BaseImageBoxProps<T extends ImageBoxTag> extends WithColorPalet
48
48
  }
49
49
 
50
50
  export type ImageBoxProps<T extends ImageBoxTag> = HTMLTag[T] & BaseImageBoxProps<T>
51
+ export type ImageButtonProps = (
52
+ Omit<React.JSX.IntrinsicElements['button'], 'children'> & Omit<BaseImageBoxProps<'button'>, 'tag'>
53
+ )
54
+ export type ImageLinkProps = (
55
+ Omit<React.JSX.IntrinsicElements['a'], 'children'> & Omit<BaseImageBoxProps<'a'>, 'tag'>
56
+ )
51
57
 
52
58
  /**
53
59
  * 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
@@ -102,7 +108,7 @@ export const ImageBox = withRef(
102
108
  * ```
103
109
  */
104
110
  export const ImageButton = withRef(
105
- function ImageButton(props: Omit<ImageBoxProps<'button'>, 'tag'>) {
111
+ function ImageButton(props: ImageButtonProps) {
106
112
  return <ImageBox {...props} tag="button" type={props.type || 'button'} />
107
113
  },
108
114
  )
@@ -119,7 +125,7 @@ export const ImageButton = withRef(
119
125
  * ```
120
126
  */
121
127
  export const ImageLink = withRef(
122
- function ImageLink(props: Omit<ImageBoxProps<'a'>, 'tag'>) {
128
+ function ImageLink(props: ImageLinkProps) {
123
129
  return <ImageBox {...props} tag="a" />
124
130
  },
125
131
  )
@@ -47,9 +47,11 @@ export function Overlay<T extends keyof HTMLTag>({
47
47
  alignment = 'center',
48
48
  attributes,
49
49
  onRenderChild,
50
- autoFocusBehavior = 'keyboard',
50
+ autoFocusBehavior = 'always',
51
51
  openDelayMS,
52
52
  closeDelayMS,
53
+ onShowOverlay,
54
+ onHideOverlay,
53
55
  ...props
54
56
  }: OverlayProps<T>,
55
57
  ) {
@@ -66,7 +68,6 @@ export function Overlay<T extends keyof HTMLTag>({
66
68
  let visible = false
67
69
  let hideOnClickOutside: ((event: Event) => void) | undefined
68
70
  let hideOnEsc: ((event: Event) => void) | undefined
69
- let hideOverlay: ((immediately?: boolean) => Promise<void>) | undefined
70
71
  let removeRefocusTargetListener: (() => void) | undefined
71
72
 
72
73
  function getTarget() {
@@ -101,8 +102,11 @@ export function Overlay<T extends keyof HTMLTag>({
101
102
  alignment: dynamic.current.alignment,
102
103
  attributes: dynamic.current.attributes,
103
104
  })
104
- hideOverlay?.(true) // ensures there's no lost opened overlay.
105
- hideOverlay = hideFn
105
+
106
+ if (onShowOverlay) {
107
+ const trigger = target instanceof Event ? target.target : target
108
+ if (trigger instanceof HTMLElement) onShowOverlay(trigger, overlay)
109
+ }
106
110
 
107
111
  function onHide(condition: (event: Event) => boolean) {
108
112
  return (event: Event) => {
@@ -137,17 +141,21 @@ export function Overlay<T extends keyof HTMLTag>({
137
141
  if (autoFocusBehavior === 'always' || (autoFocusBehavior === 'keyboard' && !openedWithMouse)) {
138
142
  setTimeout(() => focusFirstChild(overlay), arbitraryRenderTime)
139
143
  }
140
- }
141
144
 
142
- controller.current.close = async (immediately = false) => {
143
- if (!immediately && (closeDelayMS ?? 0) > 0) {
144
- await delay(closeDelayMS ?? 0)
145
+ controller.current.close = async (immediately = false) => {
146
+ if (!immediately && (closeDelayMS ?? 0) > 0) {
147
+ await delay(closeDelayMS ?? 0)
148
+ }
149
+ visible = false
150
+ if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
151
+ if (hideOnEsc) document.removeEventListener('keydown', hideOnEsc)
152
+ removeRefocusTargetListener?.()
153
+ await hideFn?.()
154
+ if (onHideOverlay) {
155
+ const trigger = target instanceof Event ? target.target : target
156
+ if (trigger instanceof HTMLElement) onHideOverlay(trigger)
157
+ }
145
158
  }
146
- visible = false
147
- if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
148
- if (hideOnEsc) document.removeEventListener('keydown', hideOnEsc)
149
- removeRefocusTargetListener?.()
150
- await hideOverlay?.()
151
159
  }
152
160
 
153
161
  if (triggerOn === 'hover') {
@@ -172,11 +180,9 @@ export function Overlay<T extends keyof HTMLTag>({
172
180
  if (hideOnClickOutside) document.removeEventListener('click', hideOnClickOutside)
173
181
  }
174
182
  }
175
- }, [wrapper.current, triggerOn])
176
183
 
177
- useEffect(() => () => {
178
- controller.current?.close(true)
179
- }, [])
184
+ return () => controller.current.close(true) // prevents orfan tooltips
185
+ }, [wrapper.current, triggerOn])
180
186
 
181
187
  return <div ref={wrapper} {...props}>{children}</div>
182
188
  }
@@ -32,6 +32,8 @@ export const RichSelect = withRef(
32
32
  onBlur,
33
33
  showArrow,
34
34
  placeholder,
35
+ name,
36
+ selectAttributes,
35
37
  ...props
36
38
  }: SelectProps<T> & { type?: 'rich' },
37
39
  ) {
@@ -80,6 +82,7 @@ export const RichSelect = withRef(
80
82
  {...props}
81
83
  >
82
84
  <SimpleSelect
85
+ selectAttributes={selectAttributes}
83
86
  options={options}
84
87
  value={value}
85
88
  renderLabel={renderLabel}
@@ -90,6 +93,7 @@ export const RichSelect = withRef(
90
93
  onFocus={onFocus}
91
94
  onBlur={onBlur}
92
95
  wrap={false}
96
+ name={name}
93
97
  />
94
98
  <header
95
99
  onClick={() => {
@@ -19,6 +19,8 @@ export const SimpleSelect = withRef(
19
19
  onBlur,
20
20
  onFocus,
21
21
  wrap,
22
+ name,
23
+ selectAttributes,
22
24
  ...props
23
25
  }: SelectProps<T> & { wrap?: boolean },
24
26
  ) {
@@ -42,6 +44,7 @@ export const SimpleSelect = withRef(
42
44
 
43
45
  return wrap === false ? (
44
46
  <CitricComponent
47
+ {...selectAttributes}
45
48
  tag="select"
46
49
  ref={ref as MutableRefObject<HTMLSelectElement | null>}
47
50
  required={required}
@@ -51,18 +54,21 @@ export const SimpleSelect = withRef(
51
54
  onFocus={onFocus}
52
55
  onBlur={onBlur}
53
56
  value={value ? renderKey(value) : ''}
57
+ name={name}
54
58
  >
55
59
  {renderedOptions}
56
60
  </CitricComponent>
57
61
  ) : (
58
62
  <CitricComponent ref={ref as MutableRefObject<HTMLDivElement | null>} tag="div" component="select" aria-busy={loading} {...props}>
59
63
  <select
64
+ {...selectAttributes}
60
65
  value={value ? renderKey(value) : undefined}
61
66
  required={required}
62
67
  onChange={handleChange}
63
68
  disabled={disabled || loading}
64
69
  onFocus={onFocus}
65
70
  onBlur={onBlur}
71
+ name={name}
66
72
  >
67
73
  {renderedOptions}
68
74
  </select>
@@ -77,6 +77,17 @@ export interface CommonSelectProps<T> extends WithColorScheme {
77
77
  * A text to show when no option is selected.
78
78
  */
79
79
  placeholder?: string,
80
+ /**
81
+ * The field's name.
82
+ */
83
+ name?: string,
84
+ /**
85
+ * Attributes for the HTML Select element.
86
+ */
87
+ selectAttributes?: Omit<
88
+ React.SelectHTMLAttributes<HTMLSelectElement>,
89
+ 'value' | 'required' | 'disabled' | 'onChange' | 'onFocus' | 'onBlur' | 'name'
90
+ >,
80
91
  }
81
92
 
82
93
  export interface SimpleSelectProps<T> extends CommonSelectProps<T> {
@@ -74,7 +74,8 @@ export const Tooltip = ({
74
74
  'data-color-palette': colorPalette,
75
75
  ...attributes,
76
76
  }}
77
- onRenderChild={e => e.setAttribute('aria-describedby', id.current)}
77
+ onShowOverlay={trigger => trigger.setAttribute('aria-describedby', id.current)}
78
+ onHideOverlay={trigger => trigger.removeAttribute('aria-describedby')}
78
79
  {...props}
79
80
  >
80
81
  {children}
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export * from './components/Accordion'
2
2
  export * from './components/Alert'
3
3
  export * from './components/AsyncContent'
4
+ export * from './components/Autocomplete'
4
5
  export * from './components/Avatar'
5
6
  export * from './components/AvatarGroup'
6
7
  export * from './components/Badge'
package/src/overlay.ts CHANGED
@@ -59,6 +59,18 @@ export interface OverlayOptions<T extends keyof HTMLTag> {
59
59
  * The attributes for the HTMLElement that will be created for the overlay.
60
60
  */
61
61
  attributes?: JSX.IntrinsicElements[T] & WithDataAttributes & { inert?: boolean },
62
+ /**
63
+ * Function to run whenever the overlay is shown.
64
+ * @param trigger the HTML Element who triggered the overlay.
65
+ * @param overlay the HTML Element corresponding to the overlay's root.
66
+ */
67
+ onShowOverlay?: (trigger: HTMLElement, overlay: HTMLElement) => void,
68
+ /**
69
+ * Function to run whenever the overlay is hidden.
70
+ * @param trigger the HTML Element who triggered the overlay.
71
+ * @param overlay the HTML Element corresponding to the overlay's root.
72
+ */
73
+ onHideOverlay?: (trigger: HTMLElement) => void,
62
74
  }
63
75
 
64
76
  interface Position {