@vectara/vectara-ui 18.1.0 → 18.2.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.
@@ -10,52 +10,61 @@ 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, useEffect, useState } from "react";
13
+ import { forwardRef, useEffect, useRef, useState } from "react";
14
14
  import { VuiBasicInput } from "./BasicInput";
15
15
  export const VuiNumberInput = forwardRef((_a, ref) => {
16
16
  var { value, onChange, max, min, step, allowUndefined } = _a, rest = __rest(_a, ["value", "onChange", "max", "min", "step", "allowUndefined"]);
17
+ // localValue (rather than binding to `value` directly) sidesteps a
18
+ // Firefox quirk: `<input type="number">` reports "" mid-decimal when the
19
+ // the user types "1,0", which would round-trip through the parent and
20
+ // erase the user's input.
17
21
  const [localValue, setLocalValue] = useState(value);
18
- // This is a hacky solution to the number input misbehaving on Firefox.
19
- // If we were to apply the value and onChange values directly to the
20
- // value and onChange props of the input, then a user who:
21
- // 1. Selects all
22
- // 2. Types 1.0
23
- // will have the input show "0" as soon as they enter a decimal point.
24
- // When that character is entered, onChange is called with undefined.
25
- // This value gets stored in the value state, which resets the value to 0.
26
- // For some reason, using a useState hook to store the value doesn't have
27
- // this problem.
22
+ // Last value exchanged with the parent. The resync effect ignores echoes
23
+ // of our own emits without it, a stale prop ("6" arriving after the
24
+ // user has already typed "65") would clobber the in-flight edit.
25
+ const lastSyncedRef = useRef(value);
28
26
  useEffect(() => {
29
- // Reflect the upstream value when it changes. Ignore 0 because that
30
- // indicates the user has entered a decimal point (Firefox workaround).
31
- // When allowUndefined is on, also ignore undefined — otherwise the
32
- // parent reflecting undefined back would clear the input mid-typing.
33
- const isUndefined = !(allowUndefined && value === undefined);
34
- if (value !== 0 && isUndefined) {
27
+ if (value !== lastSyncedRef.current) {
28
+ lastSyncedRef.current = value;
35
29
  setLocalValue(value);
36
30
  }
37
31
  }, [value]);
38
- // Propagate localValue changes upstream. Without allowUndefined, an
39
- // undefined localValue (empty input) is coerced to 0 so existing
40
- // consumers always receive a number. With allowUndefined, undefined
41
- // passes through so consumers can treat empty as "no value."
42
- useEffect(() => {
43
- onChange(allowUndefined ? localValue : localValue !== null && localValue !== void 0 ? localValue : 0);
44
- }, [localValue]);
32
+ const emit = (next) => {
33
+ const outgoing = allowUndefined ? next : next !== null && next !== void 0 ? next : 0;
34
+ lastSyncedRef.current = outgoing;
35
+ onChange(outgoing);
36
+ };
45
37
  const onChangeValue = (e) => {
46
- // Enable resetting the value to undefined.
47
- if (e.target.value === "")
48
- return setLocalValue(undefined);
38
+ if (e.target.value === "") {
39
+ setLocalValue(undefined);
40
+ emit(undefined);
41
+ return;
42
+ }
49
43
  const numberValue = Number(e.target.value);
50
- if (isNaN(numberValue))
51
- return setLocalValue(undefined);
52
- setLocalValue(Number(e.target.value));
44
+ if (isNaN(numberValue)) {
45
+ setLocalValue(undefined);
46
+ emit(undefined);
47
+ return;
48
+ }
49
+ setLocalValue(numberValue);
50
+ emit(numberValue);
53
51
  };
54
52
  const onBlur = () => {
55
- if (min !== undefined && value !== undefined && value < min)
56
- onChange(min);
57
- if (max !== undefined && value !== undefined && value > max)
58
- onChange(max);
53
+ // Clamp against the effective emitted value so empty + !allowUndefined
54
+ // (which emits 0) still clamps to min.
55
+ const current = allowUndefined ? localValue : localValue !== null && localValue !== void 0 ? localValue : 0;
56
+ if (current === undefined)
57
+ return;
58
+ if (min !== undefined && current < min) {
59
+ // Clamp min.
60
+ setLocalValue(min);
61
+ emit(min);
62
+ }
63
+ else if (max !== undefined && current > max) {
64
+ // Clamp max.
65
+ setLocalValue(max);
66
+ emit(max);
67
+ }
59
68
  };
60
69
  const props = Object.assign({ type: "number", value: localValue !== null && localValue !== void 0 ? localValue : "", onChange: onChangeValue, onBlur,
61
70
  max,
@@ -1,4 +1,13 @@
1
1
  import { LinkProps } from "../link/types";
2
+ import { BADGE_COLOR } from "../badge/Badge";
3
+ export declare const TOPIC_BUTTON_COLOR: readonly ["primary", "accent"];
4
+ export type TopicButtonColor = (typeof TOPIC_BUTTON_COLOR)[number];
5
+ export declare const TOPIC_BUTTON_STYLE: readonly ["compactRow", "compactGrid"];
6
+ export type TopicButtonStyle = (typeof TOPIC_BUTTON_STYLE)[number];
7
+ type TopicButtonBadge = {
8
+ label: string;
9
+ color: (typeof BADGE_COLOR)[number];
10
+ };
2
11
  type Props = {
3
12
  children?: React.ReactNode;
4
13
  className?: string;
@@ -8,6 +17,10 @@ type Props = {
8
17
  onClick?: () => void;
9
18
  title?: string;
10
19
  fullWidth?: boolean;
20
+ color?: TopicButtonColor;
21
+ buttonStyle?: TopicButtonStyle;
22
+ badges?: TopicButtonBadge[];
23
+ icon?: React.ReactNode;
11
24
  };
12
- export declare const VuiTopicButton: ({ children, className, href, onClick, title, fullWidth, target, track, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
25
+ export declare const VuiTopicButton: ({ children, className, href, onClick, title, fullWidth, color, buttonStyle, badges, icon, target, track, ...rest }: Props) => import("react/jsx-runtime").JSX.Element;
13
26
  export {};
@@ -9,20 +9,39 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
13
13
  import classNames from "classnames";
14
14
  import { VuiSpacer } from "../spacer/Spacer";
15
15
  import { VuiTextColor } from "../typography/TextColor";
16
16
  import { VuiTitle } from "../typography/Title";
17
17
  import { useVuiContext } from "../context/Context";
18
18
  import { getTrackingProps } from "../../utils/getTrackingProps";
19
+ import { VuiBadge } from "../badge/Badge";
20
+ import { VuiFlexContainer } from "../flex/FlexContainer";
21
+ import { VuiFlexItem } from "../flex/FlexItem";
22
+ export const TOPIC_BUTTON_COLOR = ["primary", "accent"];
23
+ export const TOPIC_BUTTON_STYLE = ["compactRow", "compactGrid"];
19
24
  export const VuiTopicButton = (_a) => {
20
- var { children, className, href, onClick, title, fullWidth, target, track } = _a, rest = __rest(_a, ["children", "className", "href", "onClick", "title", "fullWidth", "target", "track"]);
25
+ var { children, className, href, onClick, title, fullWidth, color = "primary", buttonStyle, badges, icon, target, track } = _a, rest = __rest(_a, ["children", "className", "href", "onClick", "title", "fullWidth", "color", "buttonStyle", "badges", "icon", "target", "track"]);
21
26
  const { createLink } = useVuiContext();
22
- const classes = classNames("vuiTopicButton", className, {
27
+ const classes = classNames("vuiTopicButton", `vuiTopicButton--${color}`, className, {
28
+ [`vuiTopicButton--${buttonStyle}`]: buttonStyle,
23
29
  "vuiTopicButton--fullWidth": fullWidth
24
30
  });
25
- const content = (_jsxs(_Fragment, { children: [title && (_jsxs(_Fragment, { children: [_jsx(VuiTitle, Object.assign({ size: "s" }, { children: _jsx("p", { children: _jsx(VuiTextColor, Object.assign({ color: "primary" }, { children: title })) }) })), children && _jsx(VuiSpacer, { size: "xxs" })] })), children] }));
31
+ const titleNode = title && (_jsx(VuiTitle, Object.assign({ size: buttonStyle ? "xs" : "s" }, { children: _jsx("p", { children: _jsx(VuiTextColor, Object.assign({ color: color }, { children: title })) }) })));
32
+ const badgeNodes = badges === null || badges === void 0 ? void 0 : badges.map((badge, index) => (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: _jsx(VuiBadge, Object.assign({ color: badge.color, size: "s" }, { children: badge.label })) }), index)));
33
+ let content;
34
+ if (buttonStyle === "compactRow") {
35
+ // Single line: icon, title, divider + description, with badges pushed right.
36
+ content = (_jsxs(VuiFlexContainer, Object.assign({ justifyContent: "spaceBetween", alignItems: "center", spacing: "s" }, { children: [_jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "s" }, { children: [icon && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: icon }))), _jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: titleNode })), _jsx(VuiFlexItem, Object.assign({ grow: true }, { children: children && (_jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "s" }, { children: [_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: _jsx("span", { className: "vuiTopicButton__divider", "aria-hidden": "true" }) })), _jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: children }))] }))) }))] })), _jsx(VuiFlexItem, { children: badgeNodes && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: _jsx(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "xs" }, { children: badgeNodes })) }))) })] })));
37
+ }
38
+ else if (buttonStyle === "compactGrid") {
39
+ // Square icon tile beside a single-line title, with children stacked below.
40
+ content = (_jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "s" }, { children: [icon && (_jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false, className: "vuiTopicButton__iconTile" }, { children: icon }))), _jsxs(VuiFlexItem, Object.assign({ grow: true }, { children: [titleNode, children] }))] })));
41
+ }
42
+ else {
43
+ content = (_jsx(VuiFlexContainer, Object.assign({ alignItems: "start", spacing: "m" }, { children: _jsxs(VuiFlexItem, Object.assign({ grow: true }, { children: [title && (_jsxs(_Fragment, { children: [_jsxs(VuiFlexContainer, Object.assign({ alignItems: "center", spacing: "s" }, { children: [icon, _jsx(VuiFlexItem, Object.assign({ grow: false, shrink: false }, { children: titleNode })), badgeNodes] })), children && _jsx(VuiSpacer, { size: "xxs" })] })), children] })) })));
44
+ }
26
45
  if (href) {
27
46
  return createLink(Object.assign(Object.assign({ className: classes, href,
28
47
  onClick, children: content, target }, rest), getTrackingProps(track)));
@@ -11,7 +11,6 @@
11
11
 
12
12
  &:hover {
13
13
  text-decoration: none;
14
- box-shadow: $shadowLargeEnd, var(--vui-color-primary-shade) 0 0 1px 1px;
15
14
  z-index: 1;
16
15
  }
17
16
  }
@@ -19,3 +18,27 @@
19
18
  .vuiTopicButton--fullWidth {
20
19
  width: 100%;
21
20
  }
21
+
22
+ .vuiTopicButton--primary:hover {
23
+ box-shadow: $shadowLargeEnd, var(--vui-color-primary-shade) 0 0 1px 1px;
24
+ }
25
+
26
+ .vuiTopicButton--accent:hover {
27
+ box-shadow: $shadowLargeEnd, var(--vui-color-accent-shade) 0 0 1px 1px;
28
+ }
29
+
30
+ .vuiTopicButton--compactRow {
31
+ padding: $sizeS $sizeM;
32
+ }
33
+
34
+ .vuiTopicButton--compactGrid {
35
+ padding: $sizeM;
36
+ }
37
+
38
+ // A subtle vertical rule separating the title from the description in the compact row.
39
+ .vuiTopicButton__divider {
40
+ display: block;
41
+ width: 1px;
42
+ height: $sizeM;
43
+ background-color: var(--vui-color-light-shade);
44
+ }
@@ -6000,7 +6000,6 @@ h2.react-datepicker__current-month {
6000
6000
  }
6001
6001
  .vuiTopicButton:hover {
6002
6002
  text-decoration: none;
6003
- box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px, var(--vui-color-primary-shade) 0 0 1px 1px;
6004
6003
  z-index: 1;
6005
6004
  }
6006
6005
 
@@ -6008,6 +6007,29 @@ h2.react-datepicker__current-month {
6008
6007
  width: 100%;
6009
6008
  }
6010
6009
 
6010
+ .vuiTopicButton--primary:hover {
6011
+ box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px, var(--vui-color-primary-shade) 0 0 1px 1px;
6012
+ }
6013
+
6014
+ .vuiTopicButton--accent:hover {
6015
+ box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px, var(--vui-color-accent-shade) 0 0 1px 1px;
6016
+ }
6017
+
6018
+ .vuiTopicButton--compactRow {
6019
+ padding: 12px 16px;
6020
+ }
6021
+
6022
+ .vuiTopicButton--compactGrid {
6023
+ padding: 16px;
6024
+ }
6025
+
6026
+ .vuiTopicButton__divider {
6027
+ display: block;
6028
+ width: 1px;
6029
+ height: 16px;
6030
+ background-color: var(--vui-color-light-shade);
6031
+ }
6032
+
6011
6033
  .vuiTitle {
6012
6034
  color: var(--vui-color-text);
6013
6035
  margin-bottom: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectara/vectara-ui",
3
- "version": "18.1.0",
3
+ "version": "18.2.0",
4
4
  "homepage": "./",
5
5
  "description": "Vectara's design system, codified as a React and Sass component library",
6
6
  "author": "Vectara",
@@ -0,0 +1,28 @@
1
+ import { BiEditAlt } from "react-icons/bi";
2
+ import { VuiFlexContainer, VuiFlexItem, VuiIcon, VuiText, VuiTopicButton } from "../../../lib";
3
+
4
+ export const Color = () => {
5
+ return (
6
+ <VuiFlexContainer direction="column" spacing="m">
7
+ {(["primary", "accent"] as const).map((color) => (
8
+ <VuiFlexItem key={color} grow={1}>
9
+ <VuiTopicButton
10
+ color={color}
11
+ title={`${color} topic button`}
12
+ icon={
13
+ <VuiIcon color={color}>
14
+ <BiEditAlt />
15
+ </VuiIcon>
16
+ }
17
+ fullWidth
18
+ badges={[{ label: `${color.charAt(0).toUpperCase() + color.slice(1)}`, color }]}
19
+ >
20
+ <VuiText>
21
+ <p>Colors the title and hover shadow to match the {color} color.</p>
22
+ </VuiText>
23
+ </VuiTopicButton>
24
+ </VuiFlexItem>
25
+ ))}
26
+ </VuiFlexContainer>
27
+ );
28
+ };
@@ -0,0 +1,32 @@
1
+ import { BiBook, BiData } from "react-icons/bi";
2
+ import { VuiFlexContainer, VuiFlexItem, VuiIcon, VuiText, VuiTextColor, VuiTopicButton } from "../../../lib";
3
+
4
+ const templates = [
5
+ { title: "Customer support", },
6
+ { title: "Ingest agent", icon: <BiData /> },
7
+ { title: "Tech knowledge", icon: <BiBook /> }
8
+ ];
9
+
10
+ export const CompactGrid = () => {
11
+ return (
12
+ <VuiFlexContainer spacing="m">
13
+ {templates.map((template) => (
14
+ <VuiFlexItem key={template.title} grow={1}>
15
+ <VuiTopicButton
16
+ href="#"
17
+ title={template.title}
18
+ buttonStyle="compactGrid"
19
+ icon={template.icon ? <VuiIcon color="primary">{template.icon}</VuiIcon> : undefined}
20
+ fullWidth
21
+ >
22
+ <VuiText size="xs">
23
+ <p>
24
+ <VuiTextColor color="subdued">Use template to build agents</VuiTextColor>
25
+ </p>
26
+ </VuiText>
27
+ </VuiTopicButton>
28
+ </VuiFlexItem>
29
+ ))}
30
+ </VuiFlexContainer>
31
+ );
32
+ };
@@ -0,0 +1,53 @@
1
+ import { BiData } from "react-icons/bi";
2
+ import { VuiIcon, VuiText, VuiTextColor, VuiTopicButton, VuiSpacer } from "../../../lib";
3
+
4
+ export const CompactRow = () => {
5
+ return (
6
+ <>
7
+ <VuiTopicButton
8
+ href="#"
9
+ title="Customer support"
10
+ buttonStyle="compactRow"
11
+ badges={[
12
+ { label: "Support", color: "neutral" },
13
+ { label: "Technical", color: "neutral" }
14
+ ]}
15
+ fullWidth
16
+ >
17
+ <VuiText size="s">
18
+ <p>
19
+ <VuiTextColor color="subdued">
20
+ Ingest data from multiple sources, parse and transform, and index into corpora.
21
+ </VuiTextColor>
22
+ </p>
23
+ </VuiText>
24
+ </VuiTopicButton>
25
+
26
+ <VuiSpacer size="m" />
27
+
28
+ <VuiTopicButton
29
+ href="#"
30
+ title="Ingest agent"
31
+ buttonStyle="compactRow"
32
+ icon={
33
+ <VuiIcon size="s" color="primary">
34
+ <BiData />
35
+ </VuiIcon>
36
+ }
37
+ badges={[
38
+ { label: "Indexing", color: "neutral" },
39
+ { label: "Pipelines", color: "neutral" }
40
+ ]}
41
+ fullWidth
42
+ >
43
+ <VuiText size="s">
44
+ <p>
45
+ <VuiTextColor color="subdued">
46
+ Ingest data from multiple sources, parse and transform, and index into corpora.
47
+ </VuiTextColor>
48
+ </p>
49
+ </VuiText>
50
+ </VuiTopicButton>
51
+ </>
52
+ );
53
+ };
@@ -1,6 +1,12 @@
1
1
  import { Link } from "./Link";
2
+ import { Color } from "./Color";
3
+ import { CompactRow } from "./CompactRow";
4
+ import { CompactGrid } from "./CompactGrid";
2
5
 
3
6
  const LinkSource = require("!!raw-loader!./Link");
7
+ const ColorSource = require("!!raw-loader!./Color");
8
+ const CompactRowSource = require("!!raw-loader!./CompactRow");
9
+ const CompactGridSource = require("!!raw-loader!./CompactGrid");
4
10
 
5
11
  export const topicButton = {
6
12
  name: "Topic Button",
@@ -10,6 +16,21 @@ export const topicButton = {
10
16
  name: "Link",
11
17
  component: <Link />,
12
18
  source: LinkSource.default.toString()
19
+ },
20
+ {
21
+ name: "Color",
22
+ component: <Color />,
23
+ source: ColorSource.default.toString()
24
+ },
25
+ {
26
+ name: "Compact row",
27
+ component: <CompactRow />,
28
+ source: CompactRowSource.default.toString()
29
+ },
30
+ {
31
+ name: "Compact grid",
32
+ component: <CompactGrid />,
33
+ source: CompactGridSource.default.toString()
13
34
  }
14
35
  ]
15
36
  };