@spaced-out/ui-design-system 0.1.117 → 0.1.119

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [0.1.119](https://github.com/spaced-out/ui-design-system/compare/v0.1.118...v0.1.119) (2024-08-16)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * disabled state fix for link with icon ([a0c3a2a](https://github.com/spaced-out/ui-design-system/commit/a0c3a2aa035e0639a625d51c95c3e96dabd2a7fa))
11
+
12
+ ### [0.1.118](https://github.com/spaced-out/ui-design-system/compare/v0.1.117...v0.1.118) (2024-08-05)
13
+
14
+
15
+ ### Features
16
+
17
+ * 🍪 chip enhancements ([#250](https://github.com/spaced-out/ui-design-system/issues/250)) ([a18fae8](https://github.com/spaced-out/ui-design-system/commit/a18fae893703092d6221f09043e62cc86308ae67))
18
+
5
19
  ### [0.1.117](https://github.com/spaced-out/ui-design-system/compare/v0.1.116...v0.1.117) (2024-07-30)
6
20
 
7
21
 
@@ -32,16 +32,42 @@ const Chip = /*#__PURE__*/React.forwardRef((_ref, ref) => {
32
32
  iconName = '',
33
33
  iconType = 'regular',
34
34
  showStatusIndicator,
35
- disableHoverState = false,
36
35
  dismissable = false,
37
36
  onDismiss = () => null,
38
37
  onClick,
39
38
  disabled,
40
- ...rest
39
+ disableHoverState = !onClick,
40
+ // There is no reason for hover state to be active when there is no click handler attached
41
+ ...restProps
41
42
  } = _ref;
43
+ /**
44
+ * Note (Nishant): Why we are using a `div` to render a onclick element instead of a `button`?
45
+ *
46
+ * Rendering the `Chip` component as a button directly would have been ideal, as it would
47
+ * have naturally handled interactivity and accessibility for clickable chips(which has an onClick). However,
48
+ * the `Chip` component includes a `CloseIcon`, which itself is a button. Nesting a `<button>`
49
+ * inside another `<button>` is semantically incorrect and would lead to improper HTML structure.
50
+ *
51
+ * Instead, we use a `<div>` with `role="button"` to maintain proper semantic behavior and
52
+ * avoid nesting buttons. While `role="button"` provides the appropriate semantics, it does
53
+ * not automatically handle keyboard interactions. Therefore, we manually handle `Enter`
54
+ * and `Space` key events to ensure the component is fully accessible and keyboard-compliant.
55
+ *
56
+ * Although this method might seem less conventional, it simplifies implementation and ensures
57
+ * backward compatibility while adhering to accessibility standards.
58
+ */
59
+ const handleKeyDown = event => {
60
+ const {
61
+ key
62
+ } = event;
63
+ if (key === 'Enter' || key === ' ') {
64
+ event.preventDefault(); // Prevent default action for Enter and Space keys
65
+ onClick?.(event);
66
+ }
67
+ };
42
68
  return /*#__PURE__*/React.createElement("div", _extends({
43
69
  "data-testid": "Chip"
44
- }, rest, {
70
+ }, restProps, {
45
71
  ref: ref,
46
72
  className: (0, _classify.classify)(_ChipModule.default.chipWrapper, {
47
73
  [_ChipModule.default.primary]: semantic === CHIP_SEMANTIC.primary,
@@ -57,7 +83,10 @@ const Chip = /*#__PURE__*/React.forwardRef((_ref, ref) => {
57
83
  [_ChipModule.default.disabled]: disabled,
58
84
  [_ChipModule.default.noHoverState]: showStatusIndicator || disableHoverState
59
85
  }, classNames?.wrapper),
60
- onClick: onClick
86
+ onClick: onClick,
87
+ onKeyDown: handleKeyDown,
88
+ tabIndex: showStatusIndicator || disableHoverState ? undefined : 0,
89
+ role: showStatusIndicator || disableHoverState ? undefined : 'button'
61
90
  }), showStatusIndicator && size !== 'small' && /*#__PURE__*/React.createElement(_StatusIndicator.StatusIndicator, {
62
91
  status: semantic,
63
92
  classNames: {
@@ -55,6 +55,7 @@ export type SmallChipProps = {
55
55
  };
56
56
 
57
57
  export type ChipProps = MediumChipProps | SmallChipProps;
58
+
58
59
  export const Chip: React$AbstractComponent<ChipProps, HTMLDivElement> =
59
60
  React.forwardRef<ChipProps, HTMLDivElement>(
60
61
  (
@@ -66,76 +67,105 @@ export const Chip: React$AbstractComponent<ChipProps, HTMLDivElement> =
66
67
  iconName = '',
67
68
  iconType = 'regular',
68
69
  showStatusIndicator,
69
- disableHoverState = false,
70
70
  dismissable = false,
71
71
  onDismiss = () => null,
72
72
  onClick,
73
73
  disabled,
74
- ...rest
74
+ disableHoverState = !onClick, // There is no reason for hover state to be active when there is no click handler attached
75
+ ...restProps
75
76
  }: ChipProps,
76
77
  ref,
77
- ): React.Node => (
78
- <div
79
- data-testid="Chip"
80
- {...rest}
81
- ref={ref}
82
- className={classify(
83
- css.chipWrapper,
84
- {
85
- [css.primary]: semantic === CHIP_SEMANTIC.primary,
86
- [css.information]: semantic === CHIP_SEMANTIC.information,
87
- [css.success]: semantic === CHIP_SEMANTIC.success,
88
- [css.warning]: semantic === CHIP_SEMANTIC.warning,
89
- [css.danger]: semantic === CHIP_SEMANTIC.danger,
90
- [css.secondary]: semantic === CHIP_SEMANTIC.secondary,
91
- [css.medium]: size === 'medium',
92
- [css.small]: size === 'small',
93
- [css.dismissable]: dismissable,
94
- [css.withIcon]: !!iconName && size !== 'small',
95
- [css.disabled]: disabled,
96
- [css.noHoverState]: showStatusIndicator || disableHoverState,
97
- },
98
- classNames?.wrapper,
99
- )}
100
- onClick={onClick}
101
- >
102
- {showStatusIndicator && size !== 'small' && (
103
- <StatusIndicator
104
- status={semantic}
105
- classNames={{
106
- wrapper: classify(
107
- css.statusIndicatorBlock,
108
- classNames?.statusIndicator,
109
- ),
110
- }}
111
- disabled={disabled}
112
- />
113
- )}
78
+ ): React.Node => {
79
+ /**
80
+ * Note (Nishant): Why we are using a `div` to render a onclick element instead of a `button`?
81
+ *
82
+ * Rendering the `Chip` component as a button directly would have been ideal, as it would
83
+ * have naturally handled interactivity and accessibility for clickable chips(which has an onClick). However,
84
+ * the `Chip` component includes a `CloseIcon`, which itself is a button. Nesting a `<button>`
85
+ * inside another `<button>` is semantically incorrect and would lead to improper HTML structure.
86
+ *
87
+ * Instead, we use a `<div>` with `role="button"` to maintain proper semantic behavior and
88
+ * avoid nesting buttons. While `role="button"` provides the appropriate semantics, it does
89
+ * not automatically handle keyboard interactions. Therefore, we manually handle `Enter`
90
+ * and `Space` key events to ensure the component is fully accessible and keyboard-compliant.
91
+ *
92
+ * Although this method might seem less conventional, it simplifies implementation and ensures
93
+ * backward compatibility while adhering to accessibility standards.
94
+ */
95
+ const handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
96
+ const {key} = event;
97
+ if (key === 'Enter' || key === ' ') {
98
+ event.preventDefault(); // Prevent default action for Enter and Space keys
99
+ onClick?.(event);
100
+ }
101
+ };
102
+
103
+ return (
104
+ <div
105
+ data-testid="Chip"
106
+ {...restProps}
107
+ ref={ref}
108
+ className={classify(
109
+ css.chipWrapper,
110
+ {
111
+ [css.primary]: semantic === CHIP_SEMANTIC.primary,
112
+ [css.information]: semantic === CHIP_SEMANTIC.information,
113
+ [css.success]: semantic === CHIP_SEMANTIC.success,
114
+ [css.warning]: semantic === CHIP_SEMANTIC.warning,
115
+ [css.danger]: semantic === CHIP_SEMANTIC.danger,
116
+ [css.secondary]: semantic === CHIP_SEMANTIC.secondary,
117
+ [css.medium]: size === 'medium',
118
+ [css.small]: size === 'small',
119
+ [css.dismissable]: dismissable,
120
+ [css.withIcon]: !!iconName && size !== 'small',
121
+ [css.disabled]: disabled,
122
+ [css.noHoverState]: showStatusIndicator || disableHoverState,
123
+ },
124
+ classNames?.wrapper,
125
+ )}
126
+ onClick={onClick}
127
+ onKeyDown={handleKeyDown}
128
+ tabIndex={showStatusIndicator || disableHoverState ? undefined : 0}
129
+ role={showStatusIndicator || disableHoverState ? undefined : 'button'}
130
+ >
131
+ {showStatusIndicator && size !== 'small' && (
132
+ <StatusIndicator
133
+ status={semantic}
134
+ classNames={{
135
+ wrapper: classify(
136
+ css.statusIndicatorBlock,
137
+ classNames?.statusIndicator,
138
+ ),
139
+ }}
140
+ disabled={disabled}
141
+ />
142
+ )}
114
143
 
115
- {iconName && size !== 'small' && (
116
- <Icon
117
- className={classify(css.chipIcon, classNames?.icon)}
118
- name={iconName}
119
- type={iconType}
120
- size="small"
121
- />
122
- )}
123
- <Truncate>{children}</Truncate>
144
+ {iconName && size !== 'small' && (
145
+ <Icon
146
+ className={classify(css.chipIcon, classNames?.icon)}
147
+ name={iconName}
148
+ type={iconType}
149
+ size="small"
150
+ />
151
+ )}
152
+ <Truncate>{children}</Truncate>
124
153
 
125
- {dismissable && size !== 'small' && (
126
- <CloseIcon
127
- className={css.dismissIcon}
128
- type={iconType}
129
- size="small"
130
- ariaLabel="Dismiss"
131
- onClick={(event) => {
132
- event.stopPropagation();
133
- if (!disabled && onDismiss) {
134
- onDismiss(event);
135
- }
136
- }}
137
- />
138
- )}
139
- </div>
140
- ),
154
+ {dismissable && size !== 'small' && (
155
+ <CloseIcon
156
+ className={css.dismissIcon}
157
+ type={iconType}
158
+ size="small"
159
+ ariaLabel="Dismiss"
160
+ onClick={(event) => {
161
+ event.stopPropagation();
162
+ if (!disabled && onDismiss) {
163
+ onDismiss(event);
164
+ }
165
+ }}
166
+ />
167
+ )}
168
+ </div>
169
+ );
170
+ },
141
171
  );
@@ -12,7 +12,8 @@
12
12
  colorGrayLightest,
13
13
  colorFillNone,
14
14
  colorTextDisabled,
15
- colorFillDisabled
15
+ colorFillDisabled,
16
+ colorFocusPrimary
16
17
  ) from '../../styles/variables/_color.css';
17
18
  @value (
18
19
  spaceNone,
@@ -28,6 +29,11 @@
28
29
  size26
29
30
  ) from '../../styles/variables/_size.css';
30
31
 
32
+ @value (
33
+ borderWidthNone,
34
+ borderWidthTertiary
35
+ ) from '../../styles/variables/_border.css';
36
+
31
37
  .chipWrapper {
32
38
  composes: formLabelSmall from '../../styles/typography.module.css';
33
39
  composes: motionEaseInEaseOut from '../../styles/animation.module.css';
@@ -41,6 +47,12 @@
41
47
  cursor: pointer;
42
48
  }
43
49
 
50
+ .chipWrapper:focus {
51
+ outline: none;
52
+ box-shadow: borderWidthNone borderWidthNone borderWidthNone
53
+ borderWidthTertiary colorFocusPrimary;
54
+ }
55
+
44
56
  .medium {
45
57
  height: size26;
46
58
  min-height: size26;
@@ -115,7 +115,9 @@ const Link = /*#__PURE__*/React.forwardRef((_ref, ref) => {
115
115
  name: iconLeftName,
116
116
  size: iconLeftSize,
117
117
  type: iconLeftType,
118
- className: _typographyModule.default[color]
118
+ className: (0, _classify.default)(_typographyModule.default[color], {
119
+ [_typographyModule.default.disabled]: disabled
120
+ })
119
121
  }), /*#__PURE__*/React.createElement(_ConditionalWrapper.ConditionalWrapper, {
120
122
  condition: Boolean(iconLeftName || iconRightName),
121
123
  wrapper: children => /*#__PURE__*/React.createElement("span", {
@@ -186,7 +186,7 @@ export const Link: React$AbstractComponent<LinkProps, ?HTMLAnchorElement> =
186
186
  name={iconLeftName}
187
187
  size={iconLeftSize}
188
188
  type={iconLeftType}
189
- className={css[color]}
189
+ className={classify(css[color], {[css.disabled]: disabled})}
190
190
  />
191
191
  )}
192
192
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaced-out/ui-design-system",
3
- "version": "0.1.117",
3
+ "version": "0.1.119",
4
4
  "main": "index.js",
5
5
  "description": "Sense UI components library",
6
6
  "author": {