@scality/core-ui 0.198.0 → 0.200.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.
@@ -1 +1 @@
1
- {"version":3,"file":"Buttonv2.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/buttonv2/Buttonv2.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAIlD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAW,KAAK,IAAI,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE9E,eAAO,MAAM,iBAAiB,oIAI7B,CAAC;AAEF,0DAA0D;AAC1D,KAAK,iBAAiB,GAAG,IAAI,CAC3B,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,GAAG,OAAO,CACjB,GAAG;IACF,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzD,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC/D,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wDAAwD;AACxD,KAAK,eAAe,GAAG,iBAAiB,GAAG;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;CAC1C,CAAC;AAEF,+EAA+E;AAC/E,KAAK,cAAc,GAAG,iBAAiB,GAAG;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,GAAG,CACA;IACE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACD;IACE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB,CACJ,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,eAAe,GAAG,cAAc,CAAC;AACrD,eAAO,MAAM,YAAY,2HA0KxB,CAAC;AACF,eAAO,MAAM,WAAW,0GAIvB,CAAC;AACF,eAAO,MAAM,UAAU;WAAwB,KAAK,CAAC,SAAS;SAS7D,CAAC;AAEF,eAAO,MAAM,YAAY;;;SAaxB,CAAC;AAEF,iBAAS,MAAM,CAAC,EACd,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,SAAS,EACT,GAAG,IAAI,EACR,EAAE,KAAK,2CA2CP;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"Buttonv2.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/buttonv2/Buttonv2.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAKlD,OAAO,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACpD,OAAO,EAAW,KAAK,IAAI,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE9E,eAAO,MAAM,iBAAiB,oIAI7B,CAAC;AAEF,0DAA0D;AAC1D,KAAK,iBAAiB,GAAG,IAAI,CAC3B,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,GAAG,OAAO,CACjB,GAAG;IACF,OAAO,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzD,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAC/D,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,wDAAwD;AACxD,KAAK,eAAe,GAAG,iBAAiB,GAAG;IACzC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,OAAO,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;CAC1C,CAAC;AAEF,+EAA+E;AAC/E,KAAK,cAAc,GAAG,iBAAiB,GAAG;IACxC,KAAK,CAAC,EAAE,KAAK,CAAC;CACf,GAAG,CACA;IACE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GACD;IACE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACxC,YAAY,EAAE,MAAM,CAAC;CACtB,CACJ,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG,eAAe,GAAG,cAAc,CAAC;AACrD,eAAO,MAAM,YAAY,2HA4KxB,CAAC;AACF,eAAO,MAAM,WAAW,0GAIvB,CAAC;AACF,eAAO,MAAM,UAAU;WAAwB,KAAK,CAAC,SAAS;SAS7D,CAAC;AAEF,eAAO,MAAM,YAAY;;;SAaxB,CAAC;AAEF,iBAAS,MAAM,CAAC,EACd,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAO,EACP,SAAS,EACT,GAAG,IAAI,EACR,EAAE,KAAK,2CA2CP;AAED,OAAO,EAAE,MAAM,EAAE,CAAC"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import styled, { css } from 'styled-components';
3
3
  import { spacing } from '../../spacing';
4
4
  import { fontSize, fontWeight } from '../../style/theme';
5
+ import { getContrastText } from '../../utils';
5
6
  import { Loader } from '../loader/Loader.component';
6
7
  import { Tooltip } from '../tooltip/Tooltip.component';
7
8
  export const FocusVisibleStyle = css `
@@ -31,31 +32,34 @@ export const ButtonStyled = styled.button `
31
32
  white-space: nowrap;
32
33
  height: ${(props) => (props.size === 'inline' ? spacing.r24 : spacing.r32)};
33
34
  ${(props) => {
35
+ var _a;
34
36
  const brand = props.theme;
35
37
  switch (props.variant) {
36
- case 'primary':
38
+ case 'primary': {
39
+ const primaryTextColor = (_a = getContrastText(brand.buttonPrimary, brand.textPrimary, brand.textReverse)) !== null && _a !== void 0 ? _a : brand.textPrimary;
37
40
  return css `
38
41
  background: ${brand.buttonPrimary};
39
42
  background-clip: padding-box, border-box;
40
43
  border: ${spacing.r1} solid transparent;
41
44
  border-color: ${brand.buttonPrimary};
42
- color: ${brand.textPrimary};
45
+ color: ${primaryTextColor};
43
46
  &:hover:enabled {
44
47
  cursor: pointer;
45
48
  border: ${spacing.r1} solid ${brand.infoPrimary};
46
- color: ${brand.textPrimary};
49
+ color: ${primaryTextColor};
47
50
  }
48
51
  // :focus-visible is the keyboard-only version of :focus
49
52
  &:focus-visible:enabled {
50
53
  ${FocusVisibleStyle}
51
- color: ${brand.textPrimary};
54
+ color: ${primaryTextColor};
52
55
  }
53
56
  &:active:enabled {
54
57
  cursor: pointer;
55
- color: ${brand.textPrimary};
58
+ color: ${primaryTextColor};
56
59
  border: ${spacing.r1} solid ${brand.infoSecondary};
57
60
  }
58
61
  `;
62
+ }
59
63
  case 'secondary':
60
64
  return css `
61
65
  background: ${brand.buttonSecondary};
@@ -134,5 +134,6 @@ export declare const iconTable: {
134
134
  ThumbsDown: string;
135
135
  Sidebar: string;
136
136
  Bookopen: string;
137
+ Book: string;
137
138
  };
138
139
  //# sourceMappingURL=iconTable.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"iconTable.d.ts","sourceRoot":"","sources":["../../../src/lib/components/icon/iconTable.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgIrB,CAAC"}
1
+ {"version":3,"file":"iconTable.d.ts","sourceRoot":"","sources":["../../../src/lib/components/icon/iconTable.ts"],"names":[],"mappings":"AACA;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiIrB,CAAC"}
@@ -134,4 +134,5 @@ export const iconTable = {
134
134
  ThumbsDown: 'far faThumbsDown',
135
135
  Sidebar: 'fas faTableColumns',
136
136
  Bookopen: 'fas faBookOpen',
137
+ Book: 'fas faBook',
137
138
  };
@@ -1 +1 @@
1
- {"version":3,"file":"Navbar.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/navbar/Navbar.component.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,EAA6B,KAAK,KAAK,IAAI,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAEtG,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB,GAAG,WAAW,CAAC;AAChB,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF,KAAK,MAAM,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,CAAC;AAC3D,KAAK,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AAC7B,KAAK,GAAG,GAAG;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,KAAK,GAAG;IAClB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CACnB,CAAC;AAmKF,iBAAS,MAAM,CAAC,EACd,aAAa,EACb,IAAI,EACJ,IAAS,EACT,YAAiB,EACjB,GAAG,IAAI,EACR,EAAE,KAAK,2CAuDP;AAED,eAAO,MAAM,MAAM,eAAS,CAAC"}
1
+ {"version":3,"file":"Navbar.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/navbar/Navbar.component.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,EAA6B,KAAK,KAAK,IAAI,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAEtG,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB,GAAG,WAAW,CAAC;AAChB,KAAK,cAAc,GAAG;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;CACpB,CAAC;AAEF,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B,CAAC;AAEF,KAAK,MAAM,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,CAAC;AAC3D,KAAK,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;AAC7B,KAAK,GAAG,GAAG;IACT,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;IAC9B,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;CACtB,CAAC;AACF,MAAM,MAAM,KAAK,GAAG;IAClB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,YAAY,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;CACnB,CAAC;AAwKF,iBAAS,MAAM,CAAC,EACd,aAAa,EACb,IAAI,EACJ,IAAS,EACT,YAAiB,EACjB,GAAG,IAAI,EACR,EAAE,KAAK,2CAuDP;AAED,eAAO,MAAM,MAAM,eAAS,CAAC"}
@@ -4,20 +4,21 @@ import styled, { css } from 'styled-components';
4
4
  import { Logo } from '../../icons/branding';
5
5
  import { spacing } from '../../spacing';
6
6
  import { fontSize, navbarHeight, navbarItemWidth } from '../../style/theme';
7
- import { getThemePropSelector } from '../../utils';
7
+ import { getContrastText, getThemePropSelector } from '../../utils';
8
8
  import { Dropdown } from '../dropdown/Dropdown.component';
9
9
  import { Icon } from '../icon/Icon.component';
10
10
  import { Button, FocusVisibleStyle } from '../buttonv2/Buttonv2.component';
11
+ const getNavbarTextColor = (props) => { var _a; return (_a = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse)) !== null && _a !== void 0 ? _a : props.theme.textPrimary; };
11
12
  const NavbarContainer = styled.div `
12
13
  height: ${navbarHeight};
13
14
  display: flex;
14
15
  justify-content: space-between;
15
16
  ${css `
16
17
  background-color: ${getThemePropSelector('navbarBackground')};
17
- color: ${getThemePropSelector('textPrimary')};
18
+ color: ${getNavbarTextColor};
18
19
  .fas,
19
20
  .sc-trigger-text {
20
- color: ${getThemePropSelector('textPrimary')};
21
+ color: ${getNavbarTextColor};
21
22
  }
22
23
  box-sizing: border-box;
23
24
  border-bottom: 0.5px solid ${(props) => props.theme.backgroundLevel2};
@@ -45,21 +46,23 @@ const NavbarTabs = styled.div `
45
46
  border-bottom: ${spacing.r2} solid transparent;
46
47
  border-top: ${spacing.r2} solid transparent;
47
48
  ${(props) => {
49
+ var _a;
48
50
  const { selectedActive } = props.theme;
51
+ const navTextColor = (_a = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse)) !== null && _a !== void 0 ? _a : props.theme.textPrimary;
49
52
  return css `
50
- color: ${getThemePropSelector('textPrimary')};
53
+ color: ${navTextColor};
51
54
  &:hover {
52
55
  background-color: ${getThemePropSelector('highlight')};
53
56
  }
54
57
  &.selected {
55
- color: ${getThemePropSelector('textPrimary')};
58
+ color: ${navTextColor};
56
59
  font-weight: bold;
57
60
  border-bottom-color: ${selectedActive};
58
61
  }
59
62
  // :focus-visible is the keyboard-only version of :focus
60
63
  &:focus-visible {
61
64
  ${FocusVisibleStyle}
62
- color: ${props.theme.textPrimary};
65
+ color: ${navTextColor};
63
66
  }
64
67
  `;
65
68
  }};
@@ -73,9 +76,10 @@ const TabItem = styled.div `
73
76
  align-items: center;
74
77
  padding: 0 ${spacing.r16};
75
78
  ${(props) => {
76
- const { textPrimary } = props.theme;
79
+ var _a;
80
+ const navTextColor = (_a = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse)) !== null && _a !== void 0 ? _a : props.theme.textPrimary;
77
81
  return css `
78
- color: ${textPrimary};
82
+ color: ${navTextColor};
79
83
  &:hover {
80
84
  border-bottom: ${spacing.r2} solid;
81
85
  border-top: ${spacing.r2} solid;
@@ -84,7 +88,7 @@ const TabItem = styled.div `
84
88
  // :focus-visible is the keyboard-only version of :focus
85
89
  &:focus-visible {
86
90
  ${FocusVisibleStyle}
87
- color: ${props.theme.textPrimary};
91
+ color: ${navTextColor};
88
92
  }
89
93
  `;
90
94
  }};
@@ -118,13 +122,14 @@ const NavbarMenuItem = styled.div `
118
122
  height: ${navbarHeight};
119
123
  font-size: ${fontSize.base};
120
124
  background-color: ${getThemePropSelector('navbarBackground')};
125
+ color: ${(props) => { var _a; return (_a = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse)) !== null && _a !== void 0 ? _a : props.theme.textPrimary; }};
121
126
  &:hover {
122
127
  background-color: ${getThemePropSelector('highlight')};
123
128
  }
124
129
  // :focus-visible is the keyboard-only version of :focus
125
130
  &:focus-visible {
126
131
  ${FocusVisibleStyle}
127
- color: ${(props) => props.theme.textPrimary};
132
+ color: ${(props) => { var _a; return (_a = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse)) !== null && _a !== void 0 ? _a : props.theme.textPrimary; }};
128
133
  }
129
134
  width: ${navbarItemWidth};
130
135
  }
package/dist/utils.d.ts CHANGED
@@ -7,6 +7,7 @@ export declare const getThemeVariantSelector: () => (props: any) => any;
7
7
  /** Returns the theme color key for a given variant (e.g. for use with Icon color prop). */
8
8
  export declare const getVariantThemeKey: (variant: string) => string;
9
9
  export declare const hex2RGB: (str: string) => [number, number, number];
10
+ export declare const getContrastText: (bgColor: string, textPrimary: string, textReverse: string) => string | null;
10
11
  export declare const convertRemToPixels: (rem: number) => number;
11
12
  type FormatISONumberOptions = {
12
13
  decimals?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.ts"],"names":[],"mappings":"AAYA;mCACmC;AACnC,eAAO,MAAM,oBAAoB,GAAI,QAAG,MAAM,UAAK,QAGlD,CAAC;AAEF;mCACmC;AACnC,eAAO,MAAM,uBAAuB,SAAU,UAAK,QAIlD,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MACjB,CAAC;AAErC,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAc5D,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAahD,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,EACb,UAAS,sBAA2B,KACnC,MAoBF,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/lib/utils.ts"],"names":[],"mappings":"AAYA;mCACmC;AACnC,eAAO,MAAM,oBAAoB,GAAI,QAAG,MAAM,UAAK,QAGlD,CAAC;AAEF;mCACmC;AACnC,eAAO,MAAM,uBAAuB,SAAU,UAAK,QAIlD,CAAC;AAEF,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,GAAI,SAAS,MAAM,KAAG,MACjB,CAAC;AAErC,eAAO,MAAM,OAAO,GAAI,KAAK,MAAM,KAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAc5D,CAAC;AAmBF,eAAO,MAAM,eAAe,GAC1B,SAAS,MAAM,EACf,aAAa,MAAM,EACnB,aAAa,MAAM,KAClB,MAAM,GAAG,IASX,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,KAAG,MAahD,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,OAAO,MAAM,EACb,UAAS,sBAA2B,KACnC,MAoBF,CAAC"}
package/dist/utils.js CHANGED
@@ -37,6 +37,30 @@ export const hex2RGB = (str) => {
37
37
  }
38
38
  throw new Error('Invalid hex string provided');
39
39
  };
40
+ // WCAG 2.0 relative luminance
41
+ const relativeLuminance = (r, g, b) => {
42
+ const [rs, gs, bs] = [r, g, b].map((c) => {
43
+ const s = c / 255;
44
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
45
+ });
46
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
47
+ };
48
+ const wcagContrastRatio = (l1, l2) => (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
49
+ const luminanceOf = (hex) => {
50
+ const [r, g, b] = hex2RGB(hex);
51
+ return relativeLuminance(r, g, b);
52
+ };
53
+ export const getContrastText = (bgColor, textPrimary, textReverse) => {
54
+ try {
55
+ const bgLum = luminanceOf(bgColor);
56
+ const primaryContrast = wcagContrastRatio(luminanceOf(textPrimary), bgLum);
57
+ const reverseContrast = wcagContrastRatio(luminanceOf(textReverse), bgLum);
58
+ return reverseContrast > primaryContrast ? textReverse : textPrimary;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ };
40
64
  export const convertRemToPixels = (rem) => {
41
65
  if (document.documentElement &&
42
66
  rem &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scality/core-ui",
3
- "version": "0.198.0",
3
+ "version": "0.200.0",
4
4
  "description": "Scality common React component library",
5
5
  "author": "Scality Engineering",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -2,6 +2,7 @@ import type { ButtonHTMLAttributes } from 'react';
2
2
  import styled, { css } from 'styled-components';
3
3
  import { spacing } from '../../spacing';
4
4
  import { fontSize, fontWeight } from '../../style/theme';
5
+ import { getContrastText } from '../../utils';
5
6
  import { Loader } from '../loader/Loader.component';
6
7
  import { Tooltip, Props as TooltipProps } from '../tooltip/Tooltip.component';
7
8
 
@@ -71,29 +72,31 @@ export const ButtonStyled = styled.button<ButtonStyledProps>`
71
72
  const brand = props.theme;
72
73
 
73
74
  switch (props.variant) {
74
- case 'primary':
75
+ case 'primary': {
76
+ const primaryTextColor = getContrastText(brand.buttonPrimary, brand.textPrimary, brand.textReverse) ?? brand.textPrimary;
75
77
  return css`
76
78
  background: ${brand.buttonPrimary};
77
79
  background-clip: padding-box, border-box;
78
80
  border: ${spacing.r1} solid transparent;
79
81
  border-color: ${brand.buttonPrimary};
80
- color: ${brand.textPrimary};
82
+ color: ${primaryTextColor};
81
83
  &:hover:enabled {
82
84
  cursor: pointer;
83
85
  border: ${spacing.r1} solid ${brand.infoPrimary};
84
- color: ${brand.textPrimary};
86
+ color: ${primaryTextColor};
85
87
  }
86
88
  // :focus-visible is the keyboard-only version of :focus
87
89
  &:focus-visible:enabled {
88
90
  ${FocusVisibleStyle}
89
- color: ${brand.textPrimary};
91
+ color: ${primaryTextColor};
90
92
  }
91
93
  &:active:enabled {
92
94
  cursor: pointer;
93
- color: ${brand.textPrimary};
95
+ color: ${primaryTextColor};
94
96
  border: ${spacing.r1} solid ${brand.infoSecondary};
95
97
  }
96
98
  `;
99
+ }
97
100
 
98
101
  case 'secondary':
99
102
  return css`
@@ -1,3 +1,4 @@
1
+
1
2
  /**
2
3
  * Maps human-readable icon names to FontAwesome icon info.
3
4
  * Format: 'iconType iconClass' where:
@@ -134,4 +135,5 @@ export const iconTable = {
134
135
  ThumbsDown: 'far faThumbsDown',
135
136
  Sidebar: 'fas faTableColumns',
136
137
  Bookopen: 'fas faBookOpen',
138
+ Book: 'fas faBook',
137
139
  };
@@ -3,7 +3,7 @@ import styled, { css } from 'styled-components';
3
3
  import { Logo } from '../../icons/branding';
4
4
  import { spacing } from '../../spacing';
5
5
  import { fontSize, navbarHeight, navbarItemWidth } from '../../style/theme';
6
- import { getThemePropSelector } from '../../utils';
6
+ import { getContrastText, getThemePropSelector } from '../../utils';
7
7
  import { Dropdown, type Item } from '../dropdown/Dropdown.component';
8
8
  import { Icon } from '../icon/Icon.component';
9
9
  import { Button, FocusVisibleStyle, type Props as ButtonProps } from '../buttonv2/Buttonv2.component';
@@ -38,16 +38,19 @@ export type Props = {
38
38
  logo?: JSX.Element;
39
39
  tabs?: Array<Tab>;
40
40
  };
41
+ const getNavbarTextColor = (props) =>
42
+ getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse) ?? props.theme.textPrimary;
43
+
41
44
  const NavbarContainer = styled.div`
42
45
  height: ${navbarHeight};
43
46
  display: flex;
44
47
  justify-content: space-between;
45
48
  ${css`
46
49
  background-color: ${getThemePropSelector('navbarBackground')};
47
- color: ${getThemePropSelector('textPrimary')};
50
+ color: ${getNavbarTextColor};
48
51
  .fas,
49
52
  .sc-trigger-text {
50
- color: ${getThemePropSelector('textPrimary')};
53
+ color: ${getNavbarTextColor};
51
54
  }
52
55
  box-sizing: border-box;
53
56
  border-bottom: 0.5px solid ${(props) => props.theme.backgroundLevel2};
@@ -76,20 +79,21 @@ const NavbarTabs = styled.div`
76
79
  border-top: ${spacing.r2} solid transparent;
77
80
  ${(props) => {
78
81
  const { selectedActive } = props.theme;
82
+ const navTextColor = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse) ?? props.theme.textPrimary;
79
83
  return css`
80
- color: ${getThemePropSelector('textPrimary')};
84
+ color: ${navTextColor};
81
85
  &:hover {
82
86
  background-color: ${getThemePropSelector('highlight')};
83
87
  }
84
88
  &.selected {
85
- color: ${getThemePropSelector('textPrimary')};
89
+ color: ${navTextColor};
86
90
  font-weight: bold;
87
91
  border-bottom-color: ${selectedActive};
88
92
  }
89
93
  // :focus-visible is the keyboard-only version of :focus
90
94
  &:focus-visible {
91
95
  ${FocusVisibleStyle}
92
- color: ${props.theme.textPrimary};
96
+ color: ${navTextColor};
93
97
  }
94
98
  `;
95
99
  }};
@@ -103,9 +107,9 @@ const TabItem = styled.div<{ selected: boolean }>`
103
107
  align-items: center;
104
108
  padding: 0 ${spacing.r16};
105
109
  ${(props) => {
106
- const { textPrimary } = props.theme;
110
+ const navTextColor = getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse) ?? props.theme.textPrimary;
107
111
  return css`
108
- color: ${textPrimary};
112
+ color: ${navTextColor};
109
113
  &:hover {
110
114
  border-bottom: ${spacing.r2} solid;
111
115
  border-top: ${spacing.r2} solid;
@@ -114,7 +118,7 @@ const TabItem = styled.div<{ selected: boolean }>`
114
118
  // :focus-visible is the keyboard-only version of :focus
115
119
  &:focus-visible {
116
120
  ${FocusVisibleStyle}
117
- color: ${props.theme.textPrimary};
121
+ color: ${navTextColor};
118
122
  }
119
123
  `;
120
124
  }};
@@ -149,13 +153,14 @@ const NavbarMenuItem = styled.div`
149
153
  height: ${navbarHeight};
150
154
  font-size: ${fontSize.base};
151
155
  background-color: ${getThemePropSelector('navbarBackground')};
156
+ color: ${(props) => getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse) ?? props.theme.textPrimary};
152
157
  &:hover {
153
158
  background-color: ${getThemePropSelector('highlight')};
154
159
  }
155
160
  // :focus-visible is the keyboard-only version of :focus
156
161
  &:focus-visible {
157
162
  ${FocusVisibleStyle}
158
- color: ${(props) => props.theme.textPrimary};
163
+ color: ${(props) => getContrastText(props.theme.navbarBackground, props.theme.textPrimary, props.theme.textReverse) ?? props.theme.textPrimary};
159
164
  }
160
165
  width: ${navbarItemWidth};
161
166
  }
@@ -0,0 +1,48 @@
1
+ import { getContrastText } from './utils';
2
+
3
+ const LIGHT_TEXT = '#EAEAEA';
4
+ const DARK_TEXT = '#000000';
5
+
6
+ describe('getContrastText', () => {
7
+ it('returns textPrimary on dark backgrounds when textPrimary is light', () => {
8
+ expect(getContrastText('#000000', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
9
+ expect(getContrastText('#1A1A1A', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
10
+ expect(getContrastText('#121219', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
11
+ expect(getContrastText('#2F4185', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
12
+ });
13
+
14
+ it('returns textReverse on light backgrounds when textPrimary is light', () => {
15
+ expect(getContrastText('#FFFFFF', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
16
+ expect(getContrastText('#F5F5F5', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
17
+ expect(getContrastText('#FCFCFC', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
18
+ });
19
+
20
+ it('picks the text color with better contrast against a vivid background', () => {
21
+ expect(getContrastText('#E9041E', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
22
+ expect(getContrastText('#E9041E', '#FFFFFF', '#000000')).toBe('#FFFFFF');
23
+ });
24
+
25
+ it('handles 3-character hex shorthand', () => {
26
+ expect(getContrastText('#FFF', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
27
+ expect(getContrastText('#000', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
28
+ });
29
+
30
+ it('handles hex without # prefix', () => {
31
+ expect(getContrastText('000000', LIGHT_TEXT, DARK_TEXT)).toBe(LIGHT_TEXT);
32
+ expect(getContrastText('FFFFFF', LIGHT_TEXT, DARK_TEXT)).toBe(DARK_TEXT);
33
+ });
34
+
35
+ it('returns null for non-hex values', () => {
36
+ expect(
37
+ getContrastText(
38
+ 'linear-gradient(130deg, #9355E7 0%, #2E4AA3 60%)',
39
+ LIGHT_TEXT,
40
+ DARK_TEXT,
41
+ ),
42
+ ).toBeNull();
43
+ expect(getContrastText('not-a-color', LIGHT_TEXT, DARK_TEXT)).toBeNull();
44
+ expect(
45
+ getContrastText('rgb(255, 0, 0)', LIGHT_TEXT, DARK_TEXT),
46
+ ).toBeNull();
47
+ });
48
+ });
package/src/lib/utils.ts CHANGED
@@ -45,6 +45,38 @@ export const hex2RGB = (str: string): [number, number, number] => {
45
45
  throw new Error('Invalid hex string provided');
46
46
  };
47
47
 
48
+ // WCAG 2.0 relative luminance
49
+ const relativeLuminance = (r: number, g: number, b: number): number => {
50
+ const [rs, gs, bs] = [r, g, b].map((c) => {
51
+ const s = c / 255;
52
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
53
+ });
54
+ return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
55
+ };
56
+
57
+ const wcagContrastRatio = (l1: number, l2: number): number =>
58
+ (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
59
+
60
+ const luminanceOf = (hex: string): number => {
61
+ const [r, g, b] = hex2RGB(hex);
62
+ return relativeLuminance(r, g, b);
63
+ };
64
+
65
+ export const getContrastText = (
66
+ bgColor: string,
67
+ textPrimary: string,
68
+ textReverse: string,
69
+ ): string | null => {
70
+ try {
71
+ const bgLum = luminanceOf(bgColor);
72
+ const primaryContrast = wcagContrastRatio(luminanceOf(textPrimary), bgLum);
73
+ const reverseContrast = wcagContrastRatio(luminanceOf(textReverse), bgLum);
74
+ return reverseContrast > primaryContrast ? textReverse : textPrimary;
75
+ } catch {
76
+ return null;
77
+ }
78
+ };
79
+
48
80
  export const convertRemToPixels = (rem: number): number => {
49
81
  if (
50
82
  document.documentElement &&