@vectara/vectara-ui 16.7.0 → 16.9.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.
@@ -26,6 +26,7 @@ export type BaseButtonProps = {
26
26
  isSubmit?: boolean;
27
27
  isLoading?: boolean;
28
28
  truncate?: boolean;
29
+ "aria-label"?: string;
29
30
  };
30
31
  export declare const BaseButton: import("react").ForwardRefExoticComponent<BaseButtonProps & {
31
32
  spinnerColor?: "accent" | "primary" | "success" | "warning" | "danger" | "subdued" | "empty" | "dark" | undefined;
@@ -15,6 +15,7 @@ import classNames from "classnames";
15
15
  import { getTrackingProps } from "../../utils/getTrackingProps";
16
16
  import { useVuiContext } from "../context/Context";
17
17
  import { VuiSpinner } from "../spinner/Spinner";
18
+ import { VuiTooltip } from "../tooltip/Tooltip";
18
19
  const alignToClassMap = {
19
20
  left: "vuiBaseButton--alignLeft",
20
21
  center: "vuiBaseButton--alignCenter",
@@ -27,12 +28,13 @@ const sizeToSpinnerSizeMap = {
27
28
  l: "m"
28
29
  };
29
30
  export const BaseButton = forwardRef((_a, ref) => {
30
- var { children, icon, iconSide = "left", align = "center", className, size = "m", fullWidth, onClick, onMouseOver, onMouseOut, onMouseMove, tabIndex, isInert, isDisabled, href, target, track, htmlFor, isSubmit, isLoading, spinnerColor, truncate } = _a, rest = __rest(_a, ["children", "icon", "iconSide", "align", "className", "size", "fullWidth", "onClick", "onMouseOver", "onMouseOut", "onMouseMove", "tabIndex", "isInert", "isDisabled", "href", "target", "track", "htmlFor", "isSubmit", "isLoading", "spinnerColor", "truncate"]);
31
+ var { children, icon, iconSide = "left", align = "center", className, size = "m", fullWidth, onClick, onMouseOver, onMouseOut, onMouseMove, tabIndex, isInert, isDisabled, href, target, track, htmlFor, isSubmit, isLoading, spinnerColor, truncate, "aria-label": ariaLabel } = _a, rest = __rest(_a, ["children", "icon", "iconSide", "align", "className", "size", "fullWidth", "onClick", "onMouseOver", "onMouseOut", "onMouseMove", "tabIndex", "isInert", "isDisabled", "href", "target", "track", "htmlFor", "isSubmit", "isLoading", "spinnerColor", "truncate", "aria-label"]);
31
32
  const { createLink } = useVuiContext();
32
33
  const classes = classNames("vuiBaseButton", className, `vuiBaseButton--${size}`, alignToClassMap[align], {
33
34
  "vuiBaseButton-isInert": isInert,
34
35
  "vuiBaseButton-isDisabled": isDisabled,
35
36
  "vuiBaseButton--fullWidth": fullWidth,
37
+ "vuiBaseButton--truncate": truncate,
36
38
  [`vuiBaseButton--${isLoading ? "left" : iconSide}`]: (Boolean(icon) || isLoading) && Boolean(children)
37
39
  });
38
40
  let iconContainer;
@@ -42,33 +44,40 @@ export const BaseButton = forwardRef((_a, ref) => {
42
44
  else if (icon) {
43
45
  iconContainer = _jsx("span", Object.assign({ className: "vuiBaseButtonIconContainer" }, { children: icon }));
44
46
  }
45
- // This is useful for controlling other elements, e.g. creating an <input type="file" />
46
- // for uploading files and adding a button to trigger it.
47
+ let button;
47
48
  if (htmlFor) {
48
- return (_jsxs("label", Object.assign({ htmlFor: htmlFor, className: classes, tabIndex: tabIndex }, rest, { children: [iconContainer, children] })));
49
+ // This is useful for controlling other elements, e.g. creating an <input type="file" />
50
+ // for uploading files and adding a button to trigger it.
51
+ button = (_jsxs("label", Object.assign({ "aria-label": ariaLabel, htmlFor: htmlFor, className: classes, tabIndex: tabIndex }, rest, { children: [iconContainer, children] })));
49
52
  }
50
- // Anchor tags can't be disabled, so we'll just render a button instead
51
- // if isDisabled is true.
52
- if (href && !isDisabled) {
53
+ else if (href && !isDisabled) {
54
+ // Anchor tags can't be disabled, so we'll just render a button instead
55
+ // if isDisabled is true.
53
56
  const wrapperClasses = classNames("vuiBaseButtonLinkWrapper", {
54
57
  "vuiBaseButtonLinkWrapper--fullWidth": fullWidth
55
58
  });
56
- return createLink(Object.assign(Object.assign({ className: wrapperClasses, href,
59
+ button = createLink(Object.assign(Object.assign({ className: wrapperClasses, href,
57
60
  onClick,
58
61
  onMouseOver,
59
62
  onMouseOut,
60
63
  onMouseMove, children: (
61
64
  //* Wrap a button otherwise the flex layout breaks */}
62
- _jsxs("button", Object.assign({ className: classes, tabIndex: -1, ref: ref }, { children: [iconContainer, children] }))), target,
65
+ _jsxs("button", Object.assign({ "aria-label": ariaLabel, className: classes, tabIndex: -1, ref: ref }, { children: [iconContainer, children] }))), target,
63
66
  tabIndex }, rest), getTrackingProps(track)));
64
67
  }
65
- const labelClasses = classNames({
66
- "vuiBaseButtonLabel--truncate": truncate
67
- });
68
- const props = Object.assign({ onClick,
69
- onMouseOver,
70
- onMouseOut,
71
- onMouseMove,
72
- tabIndex, type: isSubmit ? "submit" : "button", disabled: isDisabled }, rest);
73
- return (_jsxs("button", Object.assign({ className: classes }, props, { ref: ref }, { children: [iconContainer, _jsx("span", Object.assign({ className: labelClasses }, { children: children }))] })));
68
+ else {
69
+ const labelClasses = classNames({
70
+ "vuiBaseButtonLabel--truncate": truncate
71
+ });
72
+ const props = Object.assign({ onClick,
73
+ onMouseOver,
74
+ onMouseOut,
75
+ onMouseMove,
76
+ tabIndex, type: isSubmit ? "submit" : "button", disabled: isDisabled }, rest);
77
+ button = (_jsxs("button", Object.assign({ "aria-label": ariaLabel, className: classes }, props, { ref: ref }, { children: [iconContainer, _jsx("span", Object.assign({ className: labelClasses }, { children: children }))] })));
78
+ }
79
+ if (ariaLabel) {
80
+ return _jsx(VuiTooltip, Object.assign({ tip: ariaLabel }, { children: button }));
81
+ }
82
+ return button;
74
83
  });
@@ -70,6 +70,11 @@
70
70
  width: 100%;
71
71
  }
72
72
 
73
+ .vuiBaseButton--truncate {
74
+ overflow: hidden;
75
+ text-overflow: ellipsis;
76
+ }
77
+
73
78
  // Size
74
79
  .vuiBaseButton--xs {
75
80
  font-size: $fontSizeSmall;
@@ -2,6 +2,7 @@ type Props = React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaEl
2
2
  fullWidth?: boolean;
3
3
  isInvalid?: boolean;
4
4
  resizable?: boolean;
5
+ autoGrow?: boolean;
5
6
  };
6
7
  export declare const VuiTextArea: import("react").ForwardRefExoticComponent<Omit<Props, "ref"> & import("react").RefAttributes<HTMLTextAreaElement | null>>;
7
8
  export {};
@@ -10,14 +10,39 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
- import { forwardRef } from "react";
13
+ import { forwardRef, useLayoutEffect, useRef } from "react";
14
14
  import classNames from "classnames";
15
15
  export const VuiTextArea = forwardRef((_a, ref) => {
16
- var { className, fullWidth, isInvalid, resizable } = _a, rest = __rest(_a, ["className", "fullWidth", "isInvalid", "resizable"]);
16
+ var { className, fullWidth, isInvalid, resizable, autoGrow } = _a, rest = __rest(_a, ["className", "fullWidth", "isInvalid", "resizable", "autoGrow"]);
17
17
  const classes = classNames("vuiTextArea", {
18
18
  "vuiTextArea-isInvalid": isInvalid,
19
- "vuiTextArea--fullWidth": fullWidth
19
+ "vuiTextArea--fullWidth": fullWidth,
20
+ "vuiTextArea--autoGrow": autoGrow
20
21
  }, className);
21
22
  const style = Object.assign(Object.assign({}, rest.style), { resize: resizable ? "vertical" : "none" });
22
- return _jsx("textarea", Object.assign({}, rest, { ref: ref, className: classes, style: style }));
23
+ const internalRef = useRef(null);
24
+ const setRefs = (el) => {
25
+ internalRef.current = el;
26
+ if (typeof ref === "function")
27
+ ref(el);
28
+ else if (ref)
29
+ ref.current = el;
30
+ };
31
+ // Auto-grow the textarea to fit its content. Reset to "auto" first so
32
+ // scrollHeight reflects the natural height after deletions, not the
33
+ // previously-set inline height.
34
+ useLayoutEffect(() => {
35
+ if (!autoGrow)
36
+ return;
37
+ const el = internalRef.current;
38
+ if (!el)
39
+ return;
40
+ el.style.height = "auto";
41
+ // scrollHeight excludes borders, but with box-sizing: border-box the
42
+ // assigned height includes them — without this offset the content would
43
+ // overflow by the border width and show a spurious scrollbar.
44
+ const borderY = el.offsetHeight - el.clientHeight;
45
+ el.style.height = `${el.scrollHeight + borderY}px`;
46
+ }, [autoGrow, rest.value]);
47
+ return _jsx("textarea", Object.assign({}, rest, { ref: setRefs, className: classes, style: style }));
23
48
  });
@@ -16,3 +16,9 @@
16
16
  .vuiTextArea--fullWidth {
17
17
  width: 100%;
18
18
  }
19
+
20
+ .vuiTextArea--autoGrow {
21
+ min-height: 0;
22
+ line-height: 1.6;
23
+ max-height: 12rem;
24
+ }
@@ -749,6 +749,11 @@ fieldset {
749
749
  width: 100%;
750
750
  }
751
751
 
752
+ .vuiBaseButton--truncate {
753
+ overflow: hidden;
754
+ text-overflow: ellipsis;
755
+ }
756
+
752
757
  .vuiBaseButton--xs {
753
758
  font-size: 12px;
754
759
  padding: 4px 8px;
@@ -3007,6 +3012,12 @@ h2.react-datepicker__current-month {
3007
3012
  width: 100%;
3008
3013
  }
3009
3014
 
3015
+ .vuiTextArea--autoGrow {
3016
+ min-height: 0;
3017
+ line-height: 1.6;
3018
+ max-height: 12rem;
3019
+ }
3020
+
3010
3021
  .vuiGridContainer {
3011
3022
  container-type: inline-size;
3012
3023
  width: 100%;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectara/vectara-ui",
3
- "version": "16.7.0",
3
+ "version": "16.9.0",
4
4
  "homepage": "./",
5
5
  "description": "Vectara's design system, codified as a React and Sass component library",
6
6
  "author": "Vectara",
@@ -18,7 +18,7 @@ export const Icons = () => {
18
18
  </Subsection>
19
19
 
20
20
  <Subsection title="Icon only">
21
- <VuiButtonPrimary icon={icon} color="primary" size="m" />
21
+ <VuiButtonPrimary icon={icon} color="primary" size="m" aria-label="Add to favorites" />
22
22
  </Subsection>
23
23
  </>
24
24
  );
@@ -13,7 +13,7 @@ export const Truncate = () => {
13
13
  </VuiIcon>
14
14
  }
15
15
  >
16
- A very long label that will get truncated
16
+ A very long label that will get truncated eventually if the text is long enough
17
17
  </VuiButtonSecondary>
18
18
  </div>
19
19
  );
@@ -0,0 +1,18 @@
1
+ import { useState } from "react";
2
+ import { VuiTextArea } from "../../../lib";
3
+
4
+ export const TextAreaAutoGrow = () => {
5
+ const [value, setValue] = useState<string | undefined>();
6
+
7
+ return (
8
+ <VuiTextArea
9
+ id="autoGrowTextArea"
10
+ value={value}
11
+ onChange={(event) => setValue(event.target.value)}
12
+ autoGrow
13
+ rows={1}
14
+ fullWidth
15
+ placeholder="Type a few lines — the textarea grows to fit its content"
16
+ />
17
+ );
18
+ };
@@ -1,8 +1,10 @@
1
1
  import { TextArea } from "./TextArea";
2
2
  import { TextAreaResizeable } from "./TextAreaResizeable";
3
+ import { TextAreaAutoGrow } from "./TextAreaAutoGrow";
3
4
 
4
5
  const TextAreaSource = require("!!raw-loader!./TextArea");
5
6
  const TextAreaResizeableSource = require("!!raw-loader!./TextAreaResizeable");
7
+ const TextAreaAutoGrowSource = require("!!raw-loader!./TextAreaAutoGrow");
6
8
 
7
9
  export const textArea = {
8
10
  name: "Text Area",
@@ -17,6 +19,11 @@ export const textArea = {
17
19
  name: "Resizable Text Area",
18
20
  component: <TextAreaResizeable />,
19
21
  source: TextAreaResizeableSource.default.toString()
22
+ },
23
+ {
24
+ name: "Auto-Grow Text Area",
25
+ component: <TextAreaAutoGrow />,
26
+ source: TextAreaAutoGrowSource.default.toString()
20
27
  }
21
28
  ]
22
29
  };