@mitodl/smoot-design 1.1.0 → 1.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.
Files changed (98) hide show
  1. package/dist/cjs/ai.d.ts +2 -0
  2. package/dist/cjs/ai.js +5 -0
  3. package/dist/cjs/components/AiChat/AiChat.d.ts +5 -0
  4. package/dist/cjs/components/AiChat/AiChat.js +150 -0
  5. package/dist/cjs/components/AiChat/AiChat.stories.d.ts +11 -0
  6. package/dist/cjs/components/AiChat/AiChat.stories.js +76 -0
  7. package/dist/cjs/components/AiChat/AiChat.test.d.ts +1 -0
  8. package/dist/cjs/components/AiChat/AiChat.test.js +130 -0
  9. package/dist/cjs/components/AiChat/story-utils.d.ts +3 -0
  10. package/dist/cjs/components/AiChat/story-utils.js +100 -0
  11. package/dist/cjs/components/AiChat/types.d.ts +45 -0
  12. package/dist/cjs/components/AiChat/types.js +3 -0
  13. package/dist/cjs/components/AiChat/utils.d.ts +9 -0
  14. package/dist/cjs/components/AiChat/utils.js +31 -0
  15. package/dist/cjs/components/Button/ActionButton.stories.d.ts +1 -1
  16. package/dist/cjs/components/Button/ActionButton.stories.js +20 -15
  17. package/dist/cjs/components/Button/Button.d.ts +1 -1
  18. package/dist/cjs/components/Button/Button.js +17 -10
  19. package/dist/cjs/components/Button/Button.stories.js +5 -0
  20. package/dist/cjs/components/ScrollSnap/ScrollSnap.d.ts +19 -0
  21. package/dist/cjs/components/ScrollSnap/ScrollSnap.js +57 -0
  22. package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
  23. package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.js +43 -0
  24. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
  25. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.js +43 -0
  26. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
  27. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.js +44 -0
  28. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
  29. package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.js +62 -0
  30. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
  31. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.js +33 -0
  32. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
  33. package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.js +13 -0
  34. package/dist/cjs/index.d.ts +7 -0
  35. package/dist/cjs/index.js +9 -1
  36. package/dist/cjs/jest-setup.js +15 -0
  37. package/dist/cjs/jsdom-extended.d.ts +6 -0
  38. package/dist/cjs/jsdom-extended.js +14 -0
  39. package/dist/cjs/story-utils/index.d.ts +2 -1
  40. package/dist/cjs/story-utils/index.js +8 -1
  41. package/dist/cjs/utils/composeRefs.d.ts +7 -0
  42. package/dist/cjs/utils/composeRefs.js +20 -0
  43. package/dist/cjs/utils/composeRefs.test.d.ts +1 -0
  44. package/dist/cjs/utils/composeRefs.test.js +19 -0
  45. package/dist/cjs/utils/useDevCheckStable.d.ts +8 -0
  46. package/dist/cjs/utils/useDevCheckStable.js +29 -0
  47. package/dist/cjs/utils/useInterval.d.ts +7 -0
  48. package/dist/cjs/utils/useInterval.js +25 -0
  49. package/dist/esm/ai.d.ts +2 -0
  50. package/dist/esm/ai.js +1 -0
  51. package/dist/esm/components/AiChat/AiChat.d.ts +5 -0
  52. package/dist/esm/components/AiChat/AiChat.js +147 -0
  53. package/dist/esm/components/AiChat/AiChat.stories.d.ts +11 -0
  54. package/dist/esm/components/AiChat/AiChat.stories.js +73 -0
  55. package/dist/esm/components/AiChat/AiChat.test.d.ts +1 -0
  56. package/dist/esm/components/AiChat/AiChat.test.js +128 -0
  57. package/dist/esm/components/AiChat/story-utils.d.ts +3 -0
  58. package/dist/esm/components/AiChat/story-utils.js +96 -0
  59. package/dist/esm/components/AiChat/types.d.ts +45 -0
  60. package/dist/esm/components/AiChat/types.js +2 -0
  61. package/dist/esm/components/AiChat/utils.d.ts +9 -0
  62. package/dist/esm/components/AiChat/utils.js +28 -0
  63. package/dist/esm/components/Button/ActionButton.stories.d.ts +1 -1
  64. package/dist/esm/components/Button/ActionButton.stories.js +19 -14
  65. package/dist/esm/components/Button/Button.d.ts +1 -1
  66. package/dist/esm/components/Button/Button.js +17 -10
  67. package/dist/esm/components/Button/Button.stories.js +5 -0
  68. package/dist/esm/components/ScrollSnap/ScrollSnap.d.ts +19 -0
  69. package/dist/esm/components/ScrollSnap/ScrollSnap.js +54 -0
  70. package/dist/esm/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
  71. package/dist/esm/components/ScrollSnap/ScrollSnap.stories.js +40 -0
  72. package/dist/esm/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
  73. package/dist/esm/components/SrAnnouncer/SrAnnouncer.js +40 -0
  74. package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
  75. package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.js +41 -0
  76. package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
  77. package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.js +60 -0
  78. package/dist/esm/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
  79. package/dist/esm/components/VisuallyHidden/VisuallyHidden.js +30 -0
  80. package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
  81. package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.js +10 -0
  82. package/dist/esm/index.d.ts +7 -0
  83. package/dist/esm/index.js +4 -0
  84. package/dist/esm/jest-setup.js +15 -0
  85. package/dist/esm/jsdom-extended.d.ts +6 -0
  86. package/dist/esm/jsdom-extended.js +12 -0
  87. package/dist/esm/story-utils/index.d.ts +2 -1
  88. package/dist/esm/story-utils/index.js +7 -1
  89. package/dist/esm/utils/composeRefs.d.ts +7 -0
  90. package/dist/esm/utils/composeRefs.js +17 -0
  91. package/dist/esm/utils/composeRefs.test.d.ts +1 -0
  92. package/dist/esm/utils/composeRefs.test.js +17 -0
  93. package/dist/esm/utils/useDevCheckStable.d.ts +8 -0
  94. package/dist/esm/utils/useDevCheckStable.js +26 -0
  95. package/dist/esm/utils/useInterval.d.ts +7 -0
  96. package/dist/esm/utils/useInterval.js +22 -0
  97. package/dist/tsconfig.tsbuildinfo +1 -1
  98. package/package.json +35 -18
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Links = exports.Showcase = exports.VariantsAndEdge = void 0;
3
+ exports.Showcase = exports.Links = exports.VariantsAndEdge = void 0;
4
4
  const React = require("react");
5
5
  const ActionButton_1 = require("./ActionButton");
6
6
  const Grid2_1 = require("@mui/material/Grid2");
@@ -18,6 +18,7 @@ const VARIANTS = (0, story_utils_1.enumValues)({
18
18
  primary: true,
19
19
  secondary: true,
20
20
  tertiary: true,
21
+ bordered: true,
21
22
  text: true,
22
23
  unstable_noBorder: true,
23
24
  unstable_inverted: true,
@@ -68,33 +69,22 @@ exports.VariantsAndEdge = {
68
69
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "none", variant: "primary" }), ICONS.DeleteIcon),
69
70
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "none", variant: "secondary" }), ICONS.DeleteIcon),
70
71
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "none", variant: "tertiary" }), ICONS.DeleteIcon),
72
+ React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "none", variant: "bordered" }), ICONS.DeleteIcon),
71
73
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "none", variant: "text" }), ICONS.DeleteIcon)),
72
74
  React.createElement(Stack_1.default, { direction: "row", gap: 2, sx: { my: 2 } },
73
75
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "rounded", variant: "primary" }), ICONS.DeleteIcon),
74
76
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "rounded", variant: "secondary" }), ICONS.DeleteIcon),
75
77
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "rounded", variant: "tertiary" }), ICONS.DeleteIcon),
78
+ React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "rounded", variant: "bordered" }), ICONS.DeleteIcon),
76
79
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "rounded", variant: "text" }), ICONS.DeleteIcon)),
77
80
  React.createElement(Stack_1.default, { direction: "row", gap: 2, sx: { my: 2 } },
78
81
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "circular", variant: "primary" }), ICONS.DeleteIcon),
79
82
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "circular", variant: "secondary" }), ICONS.DeleteIcon),
80
83
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "circular", variant: "tertiary" }), ICONS.DeleteIcon),
84
+ React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "circular", variant: "bordered" }), ICONS.DeleteIcon),
81
85
  React.createElement(ActionButton_1.ActionButton, Object.assign({}, args, { edge: "circular", variant: "text" }), ICONS.DeleteIcon)))),
82
86
  tags: ["main"],
83
87
  };
84
- exports.Showcase = {
85
- render: (args) => (React.createElement(Grid2_1.default, { container: true, sx: { maxWidth: "750px" }, rowGap: 2 }, STABLE_VARIANTS.flatMap((variant) => EDGES.flatMap((edge) => (React.createElement(React.Fragment, { key: `${variant}-${edge}` },
86
- React.createElement(Grid2_1.default, { size: { xs: 12, sm: 3 }, alignItems: "center" },
87
- React.createElement("code", null,
88
- "variant=",
89
- variant,
90
- React.createElement("br", null),
91
- "edge=",
92
- edge)),
93
- SIZES.flatMap((size) => Object.entries(ICONS)
94
- .filter(([_key, icon]) => icon)
95
- .map(([iconKey, icon]) => (React.createElement(Grid2_1.default, { size: { xs: 4, sm: 1 }, key: `${size}-${iconKey}` },
96
- React.createElement(ActionButton_1.ActionButton, Object.assign({ variant: variant, edge: edge, size: size }, args), icon))))))))))),
97
- };
98
88
  /**
99
89
  * `ActionButtonLink` is styled as a `ActionButton` that renders an anchor tag.
100
90
  *
@@ -107,5 +97,20 @@ exports.Links = {
107
97
  React.createElement(ActionButton_1.ActionButtonLink, { href: "#fake", variant: "primary" }, ICONS.DeleteIcon),
108
98
  React.createElement(ActionButton_1.ActionButtonLink, { href: "#fake", variant: "secondary" }, ICONS.DeleteIcon),
109
99
  React.createElement(ActionButton_1.ActionButtonLink, { href: "#fake", variant: "tertiary" }, ICONS.DeleteIcon),
100
+ React.createElement(ActionButton_1.ActionButtonLink, { href: "#fake", variant: "bordered" }, ICONS.DeleteIcon),
110
101
  React.createElement(ActionButton_1.ActionButtonLink, { href: "#fake", variant: "text" }, ICONS.DeleteIcon))),
111
102
  };
103
+ exports.Showcase = {
104
+ render: (args) => (React.createElement(Grid2_1.default, { container: true, sx: { maxWidth: "750px" }, rowGap: 2 }, STABLE_VARIANTS.flatMap((variant) => EDGES.flatMap((edge) => (React.createElement(React.Fragment, { key: `${variant}-${edge}` },
105
+ React.createElement(Grid2_1.default, { size: { xs: 12, sm: 3 }, alignItems: "center" },
106
+ React.createElement("code", null,
107
+ "variant=",
108
+ variant,
109
+ React.createElement("br", null),
110
+ "edge=",
111
+ edge)),
112
+ SIZES.flatMap((size) => Object.entries(ICONS)
113
+ .filter(([_key, icon]) => icon)
114
+ .map(([iconKey, icon]) => (React.createElement(Grid2_1.default, { size: { xs: 4, sm: 1 }, key: `${size}-${iconKey}` },
115
+ React.createElement(ActionButton_1.ActionButton, Object.assign({ variant: variant, edge: edge, size: size }, args), icon))))))))))),
116
+ };
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { LinkAdapterPropsOverrides } from "../LinkAdapter/LinkAdapter";
3
- type ButtonVariant = "primary" | "secondary" | "tertiary" | "text" | "unstable_noBorder" | "unstable_inverted" | "unstable_success";
3
+ type ButtonVariant = "primary" | "secondary" | "tertiary" | "text" | "bordered" | "unstable_noBorder" | "unstable_inverted" | "unstable_success";
4
4
  type ButtonSize = "small" | "medium" | "large";
5
5
  type ButtonEdge = "circular" | "rounded" | "none";
6
6
  type ButtonStyleProps = {
@@ -60,7 +60,7 @@ const sizeStyles = (size, hasBorder, theme) => {
60
60
  const buttonStyles = (props) => {
61
61
  const { size, variant, edge, theme, color, responsive } = Object.assign(Object.assign({}, DEFAULT_PROPS), props);
62
62
  const { colors } = theme.custom;
63
- const hasBorder = variant === "secondary";
63
+ const hasBorder = variant === "secondary" || variant === "bordered";
64
64
  return (0, react_1.css)([
65
65
  {
66
66
  color: theme.palette.text.primary,
@@ -99,11 +99,6 @@ const buttonStyles = (props) => {
99
99
  boxShadow: "none",
100
100
  },
101
101
  },
102
- hasBorder && {
103
- backgroundColor: "transparent",
104
- borderColor: "currentcolor",
105
- borderStyle: "solid",
106
- },
107
102
  variant === "unstable_success" && {
108
103
  backgroundColor: colors.darkGreen,
109
104
  color: colors.white,
@@ -119,13 +114,11 @@ const buttonStyles = (props) => {
119
114
  boxShadow: "none",
120
115
  },
121
116
  },
122
- hasBorder && {
117
+ variant === "secondary" && {
118
+ color: colors.red,
123
119
  backgroundColor: "transparent",
124
120
  borderColor: "currentcolor",
125
121
  borderStyle: "solid",
126
- },
127
- variant === "secondary" && {
128
- color: colors.red,
129
122
  ":hover:not(:disabled)": {
130
123
  // brightRed at 0.06 alpha
131
124
  backgroundColor: "rgba(255, 20, 35, 0.06)",
@@ -146,6 +139,20 @@ const buttonStyles = (props) => {
146
139
  color: colors.silverGray,
147
140
  },
148
141
  },
142
+ variant === "bordered" && {
143
+ backgroundColor: colors.white,
144
+ color: colors.silverGrayDark,
145
+ border: `1px solid ${colors.silverGrayLight}`,
146
+ ":hover:not(:disabled)": {
147
+ backgroundColor: colors.lightGray1,
148
+ color: colors.darkGray2,
149
+ },
150
+ ":disabled": {
151
+ backgroundColor: colors.lightGray2,
152
+ border: `1px solid ${colors.lightGray2}`,
153
+ color: colors.silverGrayDark,
154
+ },
155
+ },
149
156
  variant === "unstable_noBorder" && {
150
157
  backgroundColor: colors.white,
151
158
  color: colors.darkGray2,
@@ -18,6 +18,7 @@ const VARIANTS = (0, story_utils_1.enumValues)({
18
18
  primary: true,
19
19
  secondary: true,
20
20
  tertiary: true,
21
+ bordered: true,
21
22
  text: true,
22
23
  unstable_noBorder: true,
23
24
  unstable_inverted: true,
@@ -58,16 +59,19 @@ exports.VariantsAndEdge = {
58
59
  React.createElement(Button_1.Button, Object.assign({ edge: "none", variant: "primary" }, args), "Primary"),
59
60
  React.createElement(Button_1.Button, Object.assign({ edge: "none", variant: "secondary" }, args), "Secondary"),
60
61
  React.createElement(Button_1.Button, Object.assign({ edge: "none", variant: "tertiary" }, args), "Tertiary"),
62
+ React.createElement(Button_1.Button, Object.assign({ edge: "none", variant: "bordered" }, args), "Bordered"),
61
63
  React.createElement(Button_1.Button, Object.assign({ edge: "none", variant: "text" }, args), "Text")),
62
64
  React.createElement(Stack_1.default, { direction: "row", gap: 2, sx: { my: 2 } },
63
65
  React.createElement(Button_1.Button, Object.assign({ edge: "rounded", variant: "primary" }, args), "Primary"),
64
66
  React.createElement(Button_1.Button, Object.assign({ edge: "rounded", variant: "secondary" }, args), "Secondary"),
65
67
  React.createElement(Button_1.Button, Object.assign({ edge: "rounded", variant: "tertiary" }, args), "Tertiary"),
68
+ React.createElement(Button_1.Button, Object.assign({ edge: "rounded", variant: "bordered" }, args), "Bordered"),
66
69
  React.createElement(Button_1.Button, Object.assign({ edge: "rounded", variant: "text" }, args), "Text")),
67
70
  React.createElement(Stack_1.default, { direction: "row", gap: 2, sx: { my: 2 } },
68
71
  React.createElement(Button_1.Button, Object.assign({ edge: "circular", variant: "primary" }, args), "Primary"),
69
72
  React.createElement(Button_1.Button, Object.assign({ edge: "circular", variant: "secondary" }, args), "Secondary"),
70
73
  React.createElement(Button_1.Button, Object.assign({ edge: "circular", variant: "tertiary" }, args), "Tertiary"),
74
+ React.createElement(Button_1.Button, Object.assign({ edge: "circular", variant: "bordered" }, args), "Bordered"),
71
75
  React.createElement(Button_1.Button, Object.assign({ edge: "circular", variant: "text" }, args), "Text")))),
72
76
  tags: ["main"],
73
77
  };
@@ -112,6 +116,7 @@ exports.Links = {
112
116
  React.createElement(Button_1.ButtonLink, { href: "#fake", variant: "primary" }, "Link"),
113
117
  React.createElement(Button_1.ButtonLink, { href: "#fake", variant: "secondary" }, "Link"),
114
118
  React.createElement(Button_1.ButtonLink, { href: "#fake", variant: "tertiary" }, "Link"),
119
+ React.createElement(Button_1.ButtonLink, { href: "#fake", variant: "bordered" }, "Link"),
115
120
  React.createElement(Button_1.ButtonLink, { href: "#fake", variant: "text" }, "Link"))),
116
121
  };
117
122
  exports.Showcase = {
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ type ScrollSnapProps = {
3
+ /**
4
+ * Tolerance within which scroll will be considered "at the bottom" of the element.
5
+ */
6
+ threshold?: number;
7
+ /**
8
+ * Content to be displayed
9
+ */
10
+ children: React.ReactNode;
11
+ className?: string;
12
+ };
13
+ /**
14
+ * Component that automatically scrolls to the bottom of the element when new
15
+ * content is added, unless the user has scrolled up.
16
+ */
17
+ declare const ScrollSnap: React.ForwardRefExoticComponent<ScrollSnapProps & React.RefAttributes<HTMLDivElement>>;
18
+ export { ScrollSnap };
19
+ export type { ScrollSnapProps };
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScrollSnap = void 0;
4
+ const composeRefs_1 = require("@/utils/composeRefs");
5
+ const styled_1 = require("@emotion/styled");
6
+ const React = require("react");
7
+ /**
8
+ * Returns the distance between visible content and the bottom of the element.
9
+ */
10
+ const distanceFromBottom = (el) => {
11
+ return el.scrollHeight - el.clientHeight - el.scrollTop;
12
+ };
13
+ /**
14
+ * Scrolls to the bottom of the element.
15
+ */
16
+ const scrollToBottom = (el) => {
17
+ el.scrollTop = el.scrollHeight;
18
+ };
19
+ const Scroller = styled_1.default.div({
20
+ overflow: "auto",
21
+ });
22
+ /**
23
+ * Component that automatically scrolls to the bottom of the element when new
24
+ * content is added, unless the user has scrolled up.
25
+ */
26
+ const ScrollSnap = React.forwardRef(function ScrollSnap({ children, threshold = 2, className }, ref) {
27
+ const el = React.useRef();
28
+ // `content` a delayed version of children to allow measuring scroll position
29
+ // using the old children.
30
+ const [content, setContent] = React.useState(children);
31
+ const wasAtBottom = React.useRef(null);
32
+ /**
33
+ * The next two effects:
34
+ * 1. Check if the element is at the bottom.
35
+ * 2. Then set children -> content
36
+ * 3. Then scroll to bottom (if needed)
37
+ *
38
+ * In this way, we can measure the scroll position before the new content is set.
39
+ */
40
+ React.useEffect(() => {
41
+ if (!el.current)
42
+ return;
43
+ wasAtBottom.current = distanceFromBottom(el.current) < threshold;
44
+ setContent(children);
45
+ }, [children, threshold]);
46
+ React.useEffect(() => {
47
+ if (!el.current)
48
+ return;
49
+ const atBottom = distanceFromBottom(el.current) < threshold;
50
+ if (wasAtBottom.current && !atBottom) {
51
+ scrollToBottom(el.current);
52
+ wasAtBottom.current = null;
53
+ }
54
+ }, [content, threshold]);
55
+ return (React.createElement(Scroller, { className: className, ref: (0, composeRefs_1.composeRefs)(el, ref) }, content));
56
+ });
57
+ exports.ScrollSnap = ScrollSnap;
@@ -0,0 +1,6 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { ScrollSnap } from "./ScrollSnap";
3
+ declare const meta: Meta<typeof ScrollSnap>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ScrollSnap>;
6
+ export declare const Chat: Story;
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Chat = void 0;
4
+ const React = require("react");
5
+ const ScrollSnap_1 = require("./ScrollSnap");
6
+ const styled_1 = require("@emotion/styled");
7
+ const en_1 = require("@faker-js/faker/locale/en");
8
+ const useInterval_1 = require("@/utils/useInterval");
9
+ const Slider_1 = require("@mui/material/Slider");
10
+ const Stack_1 = require("@mui/material/Stack");
11
+ const Typography_1 = require("@mui/material/Typography");
12
+ const Button_1 = require("../Button/Button");
13
+ const Scroller = (0, styled_1.default)(ScrollSnap_1.ScrollSnap)({
14
+ width: "200px",
15
+ height: "350px",
16
+ border: "1pt solid black",
17
+ });
18
+ const meta = {
19
+ title: "smoot-design/ScrollSnap",
20
+ component: ScrollSnap_1.ScrollSnap,
21
+ render: function Render(args) {
22
+ const MAX = 3000;
23
+ const [updateInterval, setUpdateInterval] = React.useState(MAX);
24
+ const [children, setChildren] = React.useState(en_1.faker.lorem.sentence());
25
+ const appendText = () => setChildren((current) => {
26
+ return `${current} ${en_1.faker.lorem.sentence()}`;
27
+ });
28
+ (0, useInterval_1.useInterval)(() => {
29
+ appendText();
30
+ }, updateInterval === MAX ? null : updateInterval);
31
+ return (React.createElement(Stack_1.default, { gap: "12px", alignItems: "start" },
32
+ React.createElement(Typography_1.default, null, "Update interval (ms)"),
33
+ React.createElement(Slider_1.default, { sx: { width: "350px" }, marks: [
34
+ { value: 100, label: "0.1s" },
35
+ { value: 2000, label: "2s" },
36
+ { value: MAX, label: "off" },
37
+ ], value: updateInterval, min: 100, max: MAX, onChange: (_e, val) => setUpdateInterval(val) }),
38
+ React.createElement(Scroller, Object.assign({}, args), children),
39
+ React.createElement(Button_1.Button, { onClick: appendText }, "Append Sentence")));
40
+ },
41
+ };
42
+ exports.default = meta;
43
+ exports.Chat = {};
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ type SrAnnouncerProps = {
3
+ /**
4
+ * Message text to be read to user.
5
+ *
6
+ * Cannot contain HTML elements—only text.
7
+ */
8
+ message: string;
9
+ /**
10
+ * Messages to display while the component is in a loading state.
11
+ *
12
+ * Identical consecutive messages may not be read on some screen readers.
13
+ */
14
+ loadingMessages?: {
15
+ delay: number;
16
+ text: string;
17
+ }[];
18
+ isLoading: boolean;
19
+ };
20
+ /**
21
+ * A component that announces messages to screen readers as they come in.
22
+ */
23
+ declare const SrAnnouncer: React.FC<SrAnnouncerProps>;
24
+ export { SrAnnouncer };
25
+ export type { SrAnnouncerProps };
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SrAnnouncer = void 0;
4
+ const React = require("react");
5
+ const VisuallyHidden_1 = require("../VisuallyHidden/VisuallyHidden");
6
+ const react_1 = require("react");
7
+ const useDevCheckStable_1 = require("@/utils/useDevCheckStable");
8
+ const DEFAULT_PROPS = {
9
+ loadingMessages: [
10
+ { delay: 1500, text: "Loading" },
11
+ { delay: 4000, text: "Still loading" },
12
+ ],
13
+ };
14
+ /**
15
+ * A component that announces messages to screen readers as they come in.
16
+ */
17
+ const SrAnnouncer = ({ message, isLoading, loadingMessages = DEFAULT_PROPS.loadingMessages, }) => {
18
+ var _a;
19
+ const [loadingMsgIndex, setLoadingMsgIndex] = React.useState(-1);
20
+ /**
21
+ * If loadingMessages changes, the timeouts are reset.
22
+ * Desirable if the change is real, undesirable if it's a mistake (e.g., by
23
+ * passing an array literal as a prop).
24
+ */
25
+ (0, useDevCheckStable_1.useDevCheckStable)(loadingMessages, "SrAnnouncer: loadingMessages changed (by ===) unexpectedly. This may interfere with loading message visibility");
26
+ (0, react_1.useEffect)(() => {
27
+ setLoadingMsgIndex(-1);
28
+ }, [isLoading, loadingMessages]);
29
+ (0, react_1.useEffect)(() => {
30
+ const next = loadingMessages[loadingMsgIndex + 1];
31
+ if (!isLoading || !next)
32
+ return () => { };
33
+ const id = setTimeout(() => {
34
+ setLoadingMsgIndex(loadingMsgIndex + 1);
35
+ }, next.delay);
36
+ return () => {
37
+ clearTimeout(id);
38
+ };
39
+ }, [isLoading, loadingMsgIndex, loadingMessages]);
40
+ const loadingTxt = (_a = loadingMessages[loadingMsgIndex]) === null || _a === void 0 ? void 0 : _a.text;
41
+ return (React.createElement(VisuallyHidden_1.VisuallyHidden, { "aria-atomic": "true", "aria-live": "polite" }, isLoading ? loadingTxt : message));
42
+ };
43
+ exports.SrAnnouncer = SrAnnouncer;
@@ -0,0 +1,6 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { SrAnnouncer } from "./SrAnnouncer";
3
+ declare const meta: Meta<typeof SrAnnouncer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof SrAnnouncer>;
6
+ export declare const ScreenreaderAnnouncements: Story;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScreenreaderAnnouncements = void 0;
4
+ const React = require("react");
5
+ const SrAnnouncer_1 = require("./SrAnnouncer");
6
+ const styled_1 = require("@emotion/styled");
7
+ const Container = styled_1.default.div(({ forceVisible }) => [
8
+ forceVisible && {
9
+ width: "100% !important",
10
+ height: "100px !important",
11
+ "& > *:first-of-type": {
12
+ width: "unset !important",
13
+ height: "unset !important",
14
+ clipPath: "none !important",
15
+ clip: "unset !important",
16
+ position: "unset !important",
17
+ },
18
+ },
19
+ ]);
20
+ const meta = {
21
+ title: "smoot-design/ScreenreaderAnnouncer",
22
+ component: SrAnnouncer_1.SrAnnouncer,
23
+ decorators: function Decorator(Story) {
24
+ const [forceVisible, setForceVisible] = React.useState(true);
25
+ return (React.createElement(React.Fragment, null,
26
+ React.createElement("label", null,
27
+ "Force Visible:",
28
+ React.createElement("input", { type: "checkbox", checked: forceVisible, onChange: (e) => setForceVisible(e.target.checked) }),
29
+ React.createElement("p", null, "By default, the content of this story is visually hidden.")),
30
+ React.createElement("hr", null),
31
+ React.createElement(Container, { forceVisible: forceVisible },
32
+ React.createElement(Story, null))));
33
+ },
34
+ args: {
35
+ message: "A message to read to user",
36
+ isLoading: true,
37
+ loadingMessages: [
38
+ { delay: 1000, text: "Loading" },
39
+ { delay: 3000, text: "Still loading" },
40
+ ],
41
+ },
42
+ };
43
+ exports.default = meta;
44
+ exports.ScreenreaderAnnouncements = {};
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const React = require("react");
13
+ const react_1 = require("@testing-library/react");
14
+ const SrAnnouncer_1 = require("./SrAnnouncer");
15
+ const sleep = (ms) => {
16
+ (0, react_1.act)(() => {
17
+ jest.advanceTimersByTime(ms);
18
+ });
19
+ };
20
+ describe("SrAnnouncer", () => {
21
+ beforeEach(() => {
22
+ jest.useFakeTimers();
23
+ jest.clearAllTimers();
24
+ });
25
+ test("Renders a message when not loading", () => {
26
+ const { container } = (0, react_1.render)(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", isLoading: false }));
27
+ expect(container.textContent).toBe("Hello, world!");
28
+ });
29
+ test("Renders a loading message when loading", () => __awaiter(void 0, void 0, void 0, function* () {
30
+ const loadingMessages = [
31
+ { delay: 100, text: "Loading 1" },
32
+ { delay: 200, text: "Loading 2" },
33
+ ];
34
+ const { container, rerender } = (0, react_1.render)(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", loadingMessages: loadingMessages, isLoading: true }));
35
+ expect(container.textContent).toBe("");
36
+ sleep(100);
37
+ expect(container.textContent).toBe("Loading 1");
38
+ sleep(100);
39
+ expect(container.textContent).toBe("Loading 1");
40
+ sleep(100);
41
+ expect(container.textContent).toBe("Loading 2");
42
+ sleep(1000);
43
+ expect(container.textContent).toBe("Loading 2");
44
+ rerender(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", loadingMessages: loadingMessages, isLoading: false }));
45
+ expect(container.textContent).toBe("Hello, world!");
46
+ rerender(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", loadingMessages: loadingMessages, isLoading: true }));
47
+ expect(container.textContent).toBe("");
48
+ sleep(100);
49
+ expect(container.textContent).toBe("Loading 1");
50
+ sleep(100);
51
+ expect(container.textContent).toBe("Loading 1");
52
+ sleep(100);
53
+ expect(container.textContent).toBe("Loading 2");
54
+ }));
55
+ test("Warns if loadingMessages changes unexpectedly", () => {
56
+ const error = jest.spyOn(console, "error").mockImplementation(() => { });
57
+ const { rerender } = (0, react_1.render)(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", isLoading: true }));
58
+ rerender(React.createElement(SrAnnouncer_1.SrAnnouncer, { message: "Hello, world!", isLoading: true, loadingMessages: [{ delay: 100, text: "Loading" }] }));
59
+ expect(error).toHaveBeenCalled();
60
+ error.mockRestore();
61
+ });
62
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * VisuallyHidden is a utility component that hides its children from sighted
3
+ * users, but keeps them accessible to screen readers.
4
+ *
5
+ * Often, screenreader-only content can be handled with an `aria-label`. However,
6
+ * occasionally we need actual elements.
7
+ *
8
+ * Example:
9
+ * - a visually hidden aria-live section that reads announcements that
10
+ * isual users can ascertain in some other way.
11
+ * - a visually hidden Heading for a section whose purpose is clear for sighted users
12
+ * - a visually hidden description used for aria-describeddby
13
+ * - There is an aria-description attribute that can be used to provide a
14
+ * without an actual element on the page. However, it is introduced in
15
+ * ARIA 1.3 (working draft), not compatible with some screen readers, and
16
+ * flagged problematic by our linting.
17
+ *
18
+ * The CSS here is based on https://inclusive-components.design/tooltips-toggletips/
19
+ */
20
+ declare const VisuallyHidden: import("@emotion/styled").StyledComponent<{
21
+ theme?: import("@emotion/react").Theme;
22
+ as?: React.ElementType;
23
+ }, import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, {}>;
24
+ export { VisuallyHidden };
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VisuallyHidden = void 0;
4
+ const styled_1 = require("@emotion/styled");
5
+ /**
6
+ * VisuallyHidden is a utility component that hides its children from sighted
7
+ * users, but keeps them accessible to screen readers.
8
+ *
9
+ * Often, screenreader-only content can be handled with an `aria-label`. However,
10
+ * occasionally we need actual elements.
11
+ *
12
+ * Example:
13
+ * - a visually hidden aria-live section that reads announcements that
14
+ * isual users can ascertain in some other way.
15
+ * - a visually hidden Heading for a section whose purpose is clear for sighted users
16
+ * - a visually hidden description used for aria-describeddby
17
+ * - There is an aria-description attribute that can be used to provide a
18
+ * without an actual element on the page. However, it is introduced in
19
+ * ARIA 1.3 (working draft), not compatible with some screen readers, and
20
+ * flagged problematic by our linting.
21
+ *
22
+ * The CSS here is based on https://inclusive-components.design/tooltips-toggletips/
23
+ */
24
+ const VisuallyHidden = styled_1.default.span({
25
+ clipPath: "inset(100%)",
26
+ clip: "rect(1px, 1px, 1px, 1px)",
27
+ height: "1px",
28
+ overflow: "hidden",
29
+ position: "absolute",
30
+ whiteSpace: "nowrap",
31
+ width: "1px",
32
+ });
33
+ exports.VisuallyHidden = VisuallyHidden;
@@ -0,0 +1,6 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { VisuallyHidden } from "./VisuallyHidden";
3
+ declare const meta: Meta<typeof VisuallyHidden>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof VisuallyHidden>;
6
+ export declare const ScreenreaderOnly: Story;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScreenreaderOnly = void 0;
4
+ const VisuallyHidden_1 = require("./VisuallyHidden");
5
+ const meta = {
6
+ title: "smoot-design/VisuallyHidden",
7
+ component: VisuallyHidden_1.VisuallyHidden,
8
+ args: {
9
+ children: "Not visible, but screen readers can still read this text.",
10
+ },
11
+ };
12
+ exports.default = meta;
13
+ exports.ScreenreaderOnly = {};
@@ -7,3 +7,10 @@ export type { ButtonProps, ButtonLinkProps } from "./components/Button/Button";
7
7
  export { ActionButton, ActionButtonLink, } from "./components/Button/ActionButton";
8
8
  export type { ActionButtonProps, ActionButtonLinkProps, } from "./components/Button/ActionButton";
9
9
  export type { LinkAdapterPropsOverrides } from "./components/LinkAdapter/LinkAdapter";
10
+ export { Input } from "./components/Input/Input";
11
+ export type { InputProps } from "./components/Input/Input";
12
+ export { TextField } from "./components/TextField/TextField";
13
+ export type { TextFieldProps } from "./components/TextField/TextField";
14
+ export { SrAnnouncer } from "./components/SrAnnouncer/SrAnnouncer";
15
+ export type { SrAnnouncerProps } from "./components/SrAnnouncer/SrAnnouncer";
16
+ export { VisuallyHidden } from "./components/VisuallyHidden/VisuallyHidden";
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  "use client";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.ActionButtonLink = exports.ActionButton = exports.ButtonLink = exports.Button = exports.createTheme = exports.ThemeProvider = exports.NextJsAppRouterCacheProvider = exports.Global = exports.css = exports.styled = void 0;
4
+ exports.VisuallyHidden = exports.SrAnnouncer = exports.TextField = exports.Input = exports.ActionButtonLink = exports.ActionButton = exports.ButtonLink = exports.Button = exports.createTheme = exports.ThemeProvider = exports.NextJsAppRouterCacheProvider = exports.Global = exports.css = exports.styled = void 0;
5
5
  var styled_1 = require("@emotion/styled");
6
6
  Object.defineProperty(exports, "styled", { enumerable: true, get: function () { return styled_1.default; } });
7
7
  var react_1 = require("@emotion/react");
@@ -18,3 +18,11 @@ Object.defineProperty(exports, "ButtonLink", { enumerable: true, get: function (
18
18
  var ActionButton_1 = require("./components/Button/ActionButton");
19
19
  Object.defineProperty(exports, "ActionButton", { enumerable: true, get: function () { return ActionButton_1.ActionButton; } });
20
20
  Object.defineProperty(exports, "ActionButtonLink", { enumerable: true, get: function () { return ActionButton_1.ActionButtonLink; } });
21
+ var Input_1 = require("./components/Input/Input");
22
+ Object.defineProperty(exports, "Input", { enumerable: true, get: function () { return Input_1.Input; } });
23
+ var TextField_1 = require("./components/TextField/TextField");
24
+ Object.defineProperty(exports, "TextField", { enumerable: true, get: function () { return TextField_1.TextField; } });
25
+ var SrAnnouncer_1 = require("./components/SrAnnouncer/SrAnnouncer");
26
+ Object.defineProperty(exports, "SrAnnouncer", { enumerable: true, get: function () { return SrAnnouncer_1.SrAnnouncer; } });
27
+ var VisuallyHidden_1 = require("./components/VisuallyHidden/VisuallyHidden");
28
+ Object.defineProperty(exports, "VisuallyHidden", { enumerable: true, get: function () { return VisuallyHidden_1.VisuallyHidden; } });
@@ -1,3 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("@testing-library/jest-dom");
4
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
5
+ const failOnConsole = require("jest-fail-on-console");
6
+ failOnConsole();
7
+ beforeAll(() => {
8
+ const scrollBy = jest.fn();
9
+ HTMLElement.prototype.scrollBy = scrollBy;
10
+ });
11
+ afterEach(() => {
12
+ /**
13
+ * Clear all mock call counts between tests.
14
+ * This does NOT clear mock implementations.
15
+ * Mock implementations are always cleared between test files.
16
+ */
17
+ jest.clearAllMocks();
18
+ });
@@ -0,0 +1,6 @@
1
+ import { TestEnvironment } from "jest-environment-jsdom";
2
+ import { EnvironmentContext, JestEnvironmentConfig } from "@jest/environment";
3
+ declare class JSDOMEnvironmentExtended extends TestEnvironment {
4
+ constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
5
+ }
6
+ export default JSDOMEnvironmentExtended;