@thecb/components 12.0.4-beta.0 → 12.0.5-beta.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thecb/components",
3
- "version": "12.0.4-beta.0",
3
+ "version": "12.0.5-beta.0",
4
4
  "description": "Common lib for CityBase react components",
5
5
  "main": "dist/index.cjs.js",
6
6
  "typings": "dist/index.d.ts",
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const AccountsIconSmall = ({ themeValues }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const AccountsIconSmall = ({ themeValues, ariaHidden }) => {
6
9
  return (
7
10
  <svg
8
11
  width="22px"
@@ -12,6 +15,7 @@ const AccountsIconSmall = ({ themeValues }) => {
12
15
  xmlns="http://www.w3.org/2000/svg"
13
16
  xmlnsXlink="http://www.w3.org/1999/xlink"
14
17
  style={{ display: "inline-block", verticalAlign: "text-bottom" }}
18
+ aria-hidden={ariaHidden ? "true" : undefined}
15
19
  >
16
20
  <defs>
17
21
  <rect
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const FindIconSmall = ({ themeValues, iconIndex = 0 }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const FindIconSmall = ({ themeValues, iconIndex = 0, ariaHidden }) => {
6
9
  const maskId = `find-icon-mask-${iconIndex}`;
7
10
  return (
8
11
  <svg
@@ -11,6 +14,7 @@ const FindIconSmall = ({ themeValues, iconIndex = 0 }) => {
11
14
  viewBox="0 0 20 20"
12
15
  fill="none"
13
16
  xmlns="http://www.w3.org/2000/svg"
17
+ aria-hidden={ariaHidden ? "true" : undefined}
14
18
  >
15
19
  <path
16
20
  fillRule="evenodd"
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const HistoryIconSmall = ({ themeValues }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const HistoryIconSmall = ({ themeValues, ariaHidden }) => {
6
9
  return (
7
10
  <svg
8
11
  width="20"
@@ -10,6 +13,7 @@ const HistoryIconSmall = ({ themeValues }) => {
10
13
  viewBox="0 0 20 20"
11
14
  fill="none"
12
15
  xmlns="http://www.w3.org/2000/svg"
16
+ aria-hidden={ariaHidden ? "true" : undefined}
13
17
  >
14
18
  <path
15
19
  d="M3.33337 8.33333C3.33337 7.8731 3.70647 7.5 4.16671 7.5H15.8334C16.2936 7.5 16.6667 7.8731 16.6667 8.33333V16.6667C16.6667 17.1269 16.2936 17.5 15.8334 17.5H4.16671C3.70647 17.5 3.33337 17.1269 3.33337 16.6667V8.33333Z"
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const PropertiesIconSmall = ({ themeValues }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const PropertiesIconSmall = ({ themeValues, ariaHidden }) => {
6
9
  return (
7
10
  <svg
8
11
  xmlns="http://www.w3.org/2000/svg"
@@ -12,6 +15,7 @@ const PropertiesIconSmall = ({ themeValues }) => {
12
15
  version="1.1"
13
16
  xmlnsXlink="http://www.w3.org/1999/xlink"
14
17
  style={{ display: "inline-block", verticalAlign: "text-bottom" }}
18
+ aria-hidden={ariaHidden ? "true" : undefined}
15
19
  >
16
20
  <g fill="none" fillRule="evenodd" stroke="none" strokeWidth="1">
17
21
  <path
@@ -2,7 +2,10 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const SettingsIconSmall = ({ themeValues }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const SettingsIconSmall = ({ themeValues, ariaHidden }) => {
6
9
  return (
7
10
  <svg
8
11
  width="22px"
@@ -12,6 +15,7 @@ const SettingsIconSmall = ({ themeValues }) => {
12
15
  xmlns="http://www.w3.org/2000/svg"
13
16
  xmlnsXlink="http://www.w3.org/1999/xlink"
14
17
  style={{ display: "inline-block", verticalAlign: "text-bottom" }}
18
+ aria-hidden={ariaHidden ? "true" : undefined}
15
19
  >
16
20
  <defs>
17
21
  <rect
@@ -2,7 +2,15 @@ import React from "react";
2
2
  import { fallbackValues } from "./Icons.theme";
3
3
  import { themeComponent } from "../../../util/themeUtils";
4
4
 
5
- const WalletIconSmall = ({ themeValues, iconIndex = 0, colorOverride }) => {
5
+ // When ariaHidden is true, aria-hidden="true" is added to the SVG, which tells
6
+ // screen readers to skip this element. Use this when the icon is purely decorative
7
+ // and adjacent text already conveys its meaning (e.g. an icon inside a labeled nav link).
8
+ const WalletIconSmall = ({
9
+ themeValues,
10
+ iconIndex = 0,
11
+ colorOverride,
12
+ ariaHidden
13
+ }) => {
6
14
  return (
7
15
  <svg
8
16
  width="20"
@@ -10,6 +18,7 @@ const WalletIconSmall = ({ themeValues, iconIndex = 0, colorOverride }) => {
10
18
  viewBox="0 0 20 20"
11
19
  fill="none"
12
20
  xmlns="http://www.w3.org/2000/svg"
21
+ aria-hidden={ariaHidden ? "true" : undefined}
13
22
  >
14
23
  <path
15
24
  fillRule="evenodd"
@@ -1,5 +1,6 @@
1
1
  import React from "react";
2
2
  import { Box, Stack } from "../../atoms/layouts";
3
+ import { ExternalLink, InternalLink } from "../../atoms/link";
3
4
  import { themeComponent } from "../../../util/themeUtils";
4
5
  import { fallbackValues } from "./LinkCard.theme";
5
6
  import * as Styled from "./LinkCard.styled";
@@ -25,79 +26,102 @@ const LinkCard = ({
25
26
  const regex = /\W/g;
26
27
  const locatorSlug = title?.toLowerCase?.()?.replaceAll?.(regex, "-");
27
28
 
28
- return (
29
- <Styled.StyledAnchor
30
- key={`link-card-${locatorSlug}`}
31
- href={disabled ? undefined : href}
32
- rel={isExternalLink ? "noopener noreferrer" : undefined}
33
- target={isExternalLink ? "_blank" : undefined}
34
- tabIndex={disabled ? -1 : 0}
35
- aria-disabled={disabled}
36
- $disabled={disabled}
37
- aria-label={`${title}, ${subtitle}`}
38
- data-qa={`link-card-${locatorSlug}`}
39
- $theme={themeValues}
40
- $extraStyles={
41
- disabled ? `pointer-events: none; ${extraStyles}` : extraStyles
42
- }
43
- $hoverStyles={extraHoverStyles}
44
- $activeStyles={extraActiveStyles}
29
+ const baseStyles = Styled.getCardBaseStyles(
30
+ themeValues,
31
+ disabled,
32
+ extraStyles
33
+ );
34
+ const hoverActiveStyles = Styled.getCardHoverActiveStyles(
35
+ themeValues,
36
+ disabled,
37
+ extraHoverStyles,
38
+ extraActiveStyles
39
+ );
40
+
41
+ const cardContent = (
42
+ <Stack
43
+ childGap={0}
44
+ bottomItem={3}
45
+ justify="space-between"
46
+ style={{ width: "100%" }}
47
+ fullHeight
45
48
  >
46
49
  <Stack
47
- childGap={0}
48
- bottomItem={3}
49
- justify="space-between"
50
- style={{ width: "100%" }}
51
- fullHeight
50
+ direction="row"
51
+ childGap="0.5rem"
52
+ extraStyles={`align-items: center;`}
52
53
  >
53
- <Stack
54
- direction="row"
55
- childGap="0.5rem"
56
- extraStyles={`align-items: center;`}
54
+ <Styled.Title
55
+ variant={titleVariant}
56
+ $theme={themeValues}
57
+ margin={0}
58
+ $disabled={disabled}
57
59
  >
58
- <Styled.Title
59
- variant={titleVariant}
60
- $theme={themeValues}
61
- margin={0}
62
- $disabled={disabled}
63
- >
64
- {title}
65
- </Styled.Title>
66
- {isExternalLink && (
67
- <ExternalLinkIcon
68
- linkColor={themeValues.color}
69
- text={locatorSlug}
70
- style={{ height: "1.125rem", width: "1.125rem" }}
71
- />
72
- )}
73
- </Stack>
74
- <Box padding={subtitlePadding} width="100%">
75
- <Styled.Subtitle
76
- variant="pS"
77
- $theme={themeValues}
78
- $disabled={disabled}
79
- >
80
- {subtitle}
81
- </Styled.Subtitle>
82
- </Box>
83
- <Box
84
- background="transparent"
85
- borderWidthOverride="0 0 0 0"
86
- padding="0"
87
- width="100%"
88
- >
89
- <Styled.Footer direction="row" childGap="6px" justify="space-between">
90
- {/* To keep rightContent aligned right, use an empty Box as leftContent if none is provided */}
91
- {showLeft && !!leftContent ? (
92
- leftContent
93
- ) : (
94
- <Box extraStyles="margin-right: auto;" />
95
- )}
96
- {showRight && !!rightContent && rightContent}
97
- </Styled.Footer>
98
- </Box>
60
+ {title}
61
+ </Styled.Title>
62
+ {isExternalLink && (
63
+ <ExternalLinkIcon
64
+ linkColor={themeValues.color}
65
+ text={locatorSlug}
66
+ style={{ height: "1.125rem", width: "1.125rem" }}
67
+ />
68
+ )}
99
69
  </Stack>
100
- </Styled.StyledAnchor>
70
+ <Box padding={subtitlePadding} width="100%">
71
+ <Styled.Subtitle variant="pS" $theme={themeValues} $disabled={disabled}>
72
+ {subtitle}
73
+ </Styled.Subtitle>
74
+ </Box>
75
+ <Box
76
+ background="transparent"
77
+ borderWidthOverride="0 0 0 0"
78
+ padding="0"
79
+ width="100%"
80
+ >
81
+ <Styled.Footer direction="row" childGap="6px" justify="space-between">
82
+ {/* To keep rightContent aligned right, use an empty Box as leftContent if none is provided */}
83
+ {showLeft && !!leftContent ? (
84
+ leftContent
85
+ ) : (
86
+ <Box extraStyles="margin-right: auto;" />
87
+ )}
88
+ {showRight && !!rightContent && rightContent}
89
+ </Styled.Footer>
90
+ </Box>
91
+ </Stack>
92
+ );
93
+
94
+ if (isExternalLink) {
95
+ return (
96
+ <ExternalLink
97
+ key={`link-card-${locatorSlug}`}
98
+ href={disabled ? undefined : href}
99
+ newTab={true}
100
+ isUnderlined={false}
101
+ tabIndex={disabled ? "-1" : "0"}
102
+ ariaLabel={`${title}, ${subtitle}`}
103
+ dataQa={`link-card-${locatorSlug}`}
104
+ extraStyles={`${baseStyles} ${hoverActiveStyles}`}
105
+ >
106
+ {cardContent}
107
+ </ExternalLink>
108
+ );
109
+ }
110
+
111
+ return (
112
+ <InternalLink
113
+ key={`link-card-${locatorSlug}`}
114
+ to={disabled ? undefined : href}
115
+ isUnderlined={false}
116
+ hoverUnderline={false}
117
+ tabIndex={disabled ? "-1" : "0"}
118
+ aria-label={`${title}, ${subtitle}`}
119
+ aria-disabled={disabled}
120
+ data-qa={`link-card-${locatorSlug}`}
121
+ extraStyles={`${baseStyles} ${hoverActiveStyles}`}
122
+ >
123
+ {cardContent}
124
+ </InternalLink>
101
125
  );
102
126
  };
103
127
 
@@ -8,7 +8,6 @@ import {
8
8
  import { Box } from "../../atoms/layouts";
9
9
  import LinkCard from "./LinkCard";
10
10
  import Badge from "../../atoms/badge/Badge";
11
- import { fn } from "@storybook/test";
12
11
  import AutopayIcon from "../../atoms/icons/AutopayIcon";
13
12
  import ArrowRightIcon from "../../atoms/icons/ArrowRightIcon";
14
13
  import PlusCircleIcon from "../../atoms/icons/PlusCircleIcon";
@@ -24,11 +23,11 @@ const meta = {
24
23
  args: {
25
24
  title: "Test Workflow",
26
25
  subtitle: "Link your benefit plan",
26
+ href: "/test-workflow",
27
27
  showLeft: undefined,
28
28
  leftContent: undefined,
29
29
  showRight: undefined,
30
30
  rightContent: undefined,
31
- onClick: fn(),
32
31
  extraStyles: "",
33
32
  extraActiveStyles: "",
34
33
  extraHoverStyles: "",
@@ -51,6 +50,14 @@ const meta = {
51
50
  defaultValue: { summary: "Link your benefit plan" }
52
51
  }
53
52
  },
53
+ href: {
54
+ description:
55
+ "URL or path for the LinkCard. Internal path for InternalLink, full URL for ExternalLink",
56
+ table: {
57
+ type: { summary: "string" },
58
+ defaultValue: { summary: undefined }
59
+ }
60
+ },
54
61
  showLeft: {
55
62
  description: "Whether to show the LinkCard's left content",
56
63
  table: {
@@ -79,13 +86,6 @@ const meta = {
79
86
  defaultValue: { summary: undefined }
80
87
  }
81
88
  },
82
- onClick: {
83
- description: "Function to execute on click of LinkCard",
84
- table: {
85
- type: { summary: "function" },
86
- defaultValue: { summary: undefined }
87
- }
88
- },
89
89
  extraStyles: {
90
90
  description: "Extra styles to apply to the LinkCard",
91
91
  table: {
@@ -139,7 +139,8 @@ export default meta;
139
139
  export const BasicLinkCard = {
140
140
  args: {
141
141
  title: "Construction Permits",
142
- subtitle: "Cityville Department of Building Inspection"
142
+ subtitle: "Cityville Department of Building Inspection",
143
+ href: "/permits/construction"
143
144
  },
144
145
  render: args => {
145
146
  return (
@@ -196,7 +197,8 @@ export const BasicLinkCard = {
196
197
  export const ExternalLinkCard = {
197
198
  args: {
198
199
  title: "Construction Permits",
199
- subtitle: "Cityville Department of Building Inspection"
200
+ subtitle: "Cityville Department of Building Inspection",
201
+ href: "https://example.com/permits/construction"
200
202
  },
201
203
  render: args => {
202
204
  return (
@@ -254,7 +256,8 @@ export const ExternalLinkCard = {
254
256
  export const CompleteLinkCard = {
255
257
  args: {
256
258
  title: "Water Bills - Autopay",
257
- subittle: "Cityville Water Management"
259
+ subittle: "Cityville Water Management",
260
+ href: "/water-bills/autopay"
258
261
  },
259
262
  render: args => {
260
263
  return (
@@ -312,7 +315,8 @@ export const CompleteLinkCard = {
312
315
  export const DisabledLinkCard = {
313
316
  args: {
314
317
  title: "Property Tax - Autopay",
315
- subtitle: ""
318
+ subtitle: "",
319
+ href: "/property-tax/autopay"
316
320
  },
317
321
  render: args => {
318
322
  return (
@@ -7,16 +7,7 @@ import {
7
7
  FONT_WEIGHT_REGULAR
8
8
  } from "../../../constants/style_constants";
9
9
 
10
- export const StyledAnchor = styled("a")`
11
- ${({
12
- $disabled: disabled,
13
- $theme: theme,
14
- $extraStyles: extraStyles,
15
- $disabledStyles: disabledStyles,
16
- $hoverStyles: hoverStyles,
17
- $activeStyles: activeStyles
18
- }) => `
19
- display: flex;
10
+ export const getCardBaseStyles = (theme, disabled, extraStyles) => `
20
11
  flex-direction: column;
21
12
  align-items: flex-start;
22
13
  gap: 40px;
@@ -25,51 +16,60 @@ export const StyledAnchor = styled("a")`
25
16
  align-self: stretch;
26
17
  border-radius: 8px;
27
18
  text-decoration: none;
19
+ font-size: inherit;
20
+ color: inherit;
21
+ font-weight: inherit;
22
+ line-height: inherit;
28
23
  background-color: ${
29
24
  disabled ? theme.disabledBackgroundColor : theme.backgroundColor
30
25
  };
31
26
  border: 1px solid
32
27
  ${disabled ? theme.disabledBorderColor : theme.borderColor};
33
28
  transition: all 0.2s ease-in-out;
29
+ ${disabled ? `pointer-events: none;` : ""}
34
30
  ${extraStyles || ""}
31
+ `;
35
32
 
36
- ${
37
- disabled
38
- ? `
39
- &:hover,
40
- &:active {
41
- cursor: default;
42
- box-shadow: none;
43
- border: 1px solid ${theme.disabledBorderColor};
44
- ${disabledStyles || ""}
45
- }
46
- `
47
- : `
48
- &:hover,
49
- &:active {
50
- cursor: pointer;
51
- box-shadow: 0px 0px 0px 0px rgba(41, 42, 51, 0.1),
52
- 0px 5px 11px 0px rgba(41, 42, 51, 0.1),
53
- 0px 4px 19px 0px rgba(41, 42, 51, 0.09),
54
- 0px 27px 26px 0px rgba(41, 42, 51, 0.05),
55
- 0px 56px 31px 0px rgba(41, 42, 51, 0.01),
56
- 0px 80px 33px 0px rgba(41, 42, 51, 0);
57
- }
58
- ${hoverStyles || ""}
33
+ export const getCardHoverActiveStyles = (
34
+ theme,
35
+ disabled,
36
+ hoverStyles,
37
+ activeStyles
38
+ ) => {
39
+ if (disabled) {
40
+ return `
41
+ &:hover,
42
+ &:active {
43
+ cursor: default;
44
+ box-shadow: none;
45
+ border: 1px solid ${theme.disabledBorderColor};
46
+ }
47
+ `;
48
+ }
49
+ return `
50
+ &:hover,
51
+ &:active {
52
+ cursor: pointer;
53
+ box-shadow: 0px 0px 0px 0px rgba(41, 42, 51, 0.1),
54
+ 0px 5px 11px 0px rgba(41, 42, 51, 0.1),
55
+ 0px 4px 19px 0px rgba(41, 42, 51, 0.09),
56
+ 0px 27px 26px 0px rgba(41, 42, 51, 0.05),
57
+ 0px 56px 31px 0px rgba(41, 42, 51, 0.01),
58
+ 0px 80px 33px 0px rgba(41, 42, 51, 0);
59
+ }
60
+ ${hoverStyles || ""}
59
61
 
60
- &:hover:not(:active) {
61
- border: 1px solid ${theme.borderColor};
62
- }
62
+ &:hover:not(:active) {
63
+ border: 1px solid ${theme.borderColor};
64
+ }
63
65
 
64
- &:active {
65
- background-color: ${theme.activeBackgroundColor};
66
- border: 1px solid ${theme.borderColor};
67
- ${activeStyles || ""}
68
- }
69
- `
70
- }
71
- `}
72
- `;
66
+ &:active {
67
+ background-color: ${theme.activeBackgroundColor};
68
+ border: 1px solid ${theme.borderColor};
69
+ ${activeStyles || ""}
70
+ }
71
+ `;
72
+ };
73
73
 
74
74
  export const Title = styled(Heading)`
75
75
  display: -webkit-box;
@@ -5,16 +5,19 @@ export interface LinkCardProps {
5
5
  variant?: string; // "default" is only one
6
6
  title?: string; // title
7
7
  subtitle?: string; // beneath title
8
+ subtitlePadding?: string;
8
9
  themeValues?: any;
9
10
  showLeft?: boolean;
10
11
  leftContent?: JSX.Element;
11
12
  showRight?: boolean;
12
13
  rightContent?: JSX.Element;
13
- onClick: () => void;
14
+ href?: string;
14
15
  extraHoverStyles?: string;
15
16
  extraStyles?: string;
16
17
  extraActiveStyles?: string;
17
18
  titleVariant?: string;
19
+ disabled?: boolean;
20
+ isExternalLink?: boolean;
18
21
  }
19
22
 
20
23
  export const LinkCard: React.FC<Expand<LinkCardProps> &
@@ -57,7 +57,7 @@ const NavMenuMobile = ({
57
57
  width="100%"
58
58
  maxWidth="400px"
59
59
  padding="1rem 0.5rem"
60
- extraStyles={`position: relative; max-width: 400px; height: calc(100vh - 72px);`}
60
+ extraStyles={`position: relative; max-width: 400px; height: calc(100vh - 72px); overflow-y: auto;`}
61
61
  background={themeValues.backgroundColor}
62
62
  >
63
63
  {menuContent}