@spark-web/select 6.0.0 → 6.0.2

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
@@ -1,5 +1,35 @@
1
1
  # @spark-web/select
2
2
 
3
+ ## 6.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#789](https://github.com/brighte-labs/spark-web/pull/789)
8
+ [`56d67c3`](https://github.com/brighte-labs/spark-web/commit/56d67c310457b792d663fe4e903111c5f45be90f)
9
+ Thanks [@jacobporci-brighte](https://github.com/jacobporci-brighte)! - Apply
10
+ muted color to placeholder text when no value is selected, matching the
11
+ appearance of text-input placeholders.
12
+ - Updated dependencies
13
+ [[`56d67c3`](https://github.com/brighte-labs/spark-web/commit/56d67c310457b792d663fe4e903111c5f45be90f),
14
+ [`56d67c3`](https://github.com/brighte-labs/spark-web/commit/56d67c310457b792d663fe4e903111c5f45be90f)]:
15
+ - @spark-web/field@5.3.2
16
+ - @spark-web/text-input@6.0.2
17
+
18
+ ## 6.0.1
19
+
20
+ ### Patch Changes
21
+
22
+ - [#782](https://github.com/brighte-labs/spark-web/pull/782)
23
+ [`bb41800`](https://github.com/brighte-labs/spark-web/commit/bb418004d21165f72f4bf2456afea844ee04a21c)
24
+ Thanks [@jacobporci-brighte](https://github.com/jacobporci-brighte)! -
25
+ **docs:** add CLAUDE.md AI context files to foundation and form packages;
26
+ patch data-table with manual sort and spinner overlay patterns
27
+ - Updated dependencies
28
+ [[`bb41800`](https://github.com/brighte-labs/spark-web/commit/bb418004d21165f72f4bf2456afea844ee04a21c)]:
29
+ - @spark-web/box@6.0.1
30
+ - @spark-web/field@5.3.1
31
+ - @spark-web/text-input@6.0.1
32
+
3
33
  ## 6.0.0
4
34
 
5
35
  ### Minor Changes
package/CLAUDE.md ADDED
@@ -0,0 +1,105 @@
1
+ # @spark-web/select — AI Context
2
+
3
+ ## What this is
4
+
5
+ A native `<select>` element styled to match the Spark design system. Accepts a
6
+ flat list of options or grouped options. Must always be wrapped in a `Field`
7
+ from `@spark-web/field`. Use for filter dropdowns (role, status, state) and form
8
+ selects with a small, known set of options.
9
+
10
+ ## What this is NOT
11
+
12
+ - Not a combobox or searchable select — for searchable/async options use a
13
+ third-party combobox component with Spark styling
14
+ - Not usable without `Field` — always wrap in `<Field label="…">`
15
+ - Not responsible for its own label
16
+
17
+ ## Props interface
18
+
19
+ | Prop | Type | Notes |
20
+ | -------------- | ------------------------ | ----------------------------------------------------- |
21
+ | `options` | `Array<Option \| Group>` | required — the selectable values |
22
+ | `value` | `string \| number` | Controlled value |
23
+ | `onChange` | `ChangeEventHandler` | — |
24
+ | `placeholder` | `string` | Shown as disabled first option when no value selected |
25
+ | `defaultValue` | `string \| number` | Uncontrolled initial value |
26
+ | `name` | `string` | — |
27
+ | `required` | `boolean` | — |
28
+ | `data` | `DataAttributeMap` | Test/analytics attributes |
29
+
30
+ ### Option shape
31
+
32
+ ```ts
33
+ type Option = { label: string; value: string | number; disabled?: boolean };
34
+ type Group = { label: string; options: Option[] };
35
+ ```
36
+
37
+ `Option`, `Group`, and `OptionsOrGroups` are exported from `@spark-web/select`
38
+ and can be imported for type annotations.
39
+
40
+ ## Common patterns
41
+
42
+ ### Filter dropdown
43
+
44
+ ```tsx
45
+ <Field label="Status" labelVisibility="hidden">
46
+ <Select
47
+ placeholder="Select status"
48
+ options={[
49
+ { label: 'Active', value: 'active' },
50
+ { label: 'Inactive', value: 'inactive' },
51
+ { label: 'Suspended', value: 'suspended' },
52
+ ]}
53
+ value={statusFilter}
54
+ onChange={e => setStatusFilter(e.target.value)}
55
+ />
56
+ </Field>
57
+ ```
58
+
59
+ ### Grouped options
60
+
61
+ ```tsx
62
+ <Field label="Role">
63
+ <Select
64
+ placeholder="Select role"
65
+ options={[
66
+ {
67
+ label: 'Admin roles',
68
+ options: [
69
+ { label: 'Admin', value: 'ADMIN' },
70
+ { label: 'Admin Operations', value: 'ADMIN_OPERATIONS' },
71
+ ],
72
+ },
73
+ {
74
+ label: 'Other',
75
+ options: [{ label: 'Vendor', value: 'VENDOR' }],
76
+ },
77
+ ]}
78
+ value={role}
79
+ onChange={e => setRole(e.target.value)}
80
+ />
81
+ </Field>
82
+ ```
83
+
84
+ ### Resettable filter (with an "All" option)
85
+
86
+ ```tsx
87
+ <Select
88
+ value={filter}
89
+ onChange={e => setFilter(e.target.value)}
90
+ options={[
91
+ { label: 'All statuses', value: '' },
92
+ { label: 'Active', value: 'active' },
93
+ { label: 'Inactive', value: 'inactive' },
94
+ ]}
95
+ />
96
+ ```
97
+
98
+ ## Do NOTs
99
+
100
+ - NEVER use `Select` without a parent `Field`
101
+ - NEVER use raw `<select>` — always use `Select`
102
+ - NEVER use `Select` for more than ~20 options without a searchable combobox
103
+ - NEVER pass `disabled` directly — set it on the parent `Field`
104
+ - NEVER use `Select` for boolean toggles — use a `Checkbox` or two `Button`
105
+ options instead
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
5
6
  var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
6
7
  var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
7
8
  var react$1 = require('@emotion/react');
@@ -13,6 +14,7 @@ var theme = require('@spark-web/theme');
13
14
  var react = require('react');
14
15
  var jsxRuntime = require('@emotion/react/jsx-runtime');
15
16
 
17
+ var _excluded = ["isPlaceholderActive", "disabled"];
16
18
  var Select = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
17
19
  var data = _ref.data,
18
20
  defaultValue = _ref.defaultValue,
@@ -29,9 +31,14 @@ var Select = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
29
31
  disabled = _useFieldContext2$.disabled,
30
32
  invalid = _useFieldContext2$.invalid,
31
33
  a11yProps = _useFieldContext2[1];
34
+ // Only applies when value is controlled and explicitly empty — the component
35
+ // cannot detect when the user selects an option in uncontrolled mode, so we
36
+ // only apply the muted style when value === '' (i.e. the caller controls it).
37
+ var isPlaceholderActive = placeholder !== undefined && value === '';
32
38
  var _useSelectStyles = useSelectStyles({
33
39
  disabled: disabled,
34
- invalid: invalid
40
+ invalid: invalid,
41
+ isPlaceholderActive: isPlaceholderActive
35
42
  }),
36
43
  _useSelectStyles2 = _slicedToArray(_useSelectStyles, 2),
37
44
  boxProps = _useSelectStyles2[0],
@@ -95,7 +102,12 @@ var Indicator = function Indicator() {
95
102
  });
96
103
  };
97
104
  function useSelectStyles(props) {
98
- var _useInputStyles = textInput.useInputStyles(props),
105
+ var isPlaceholderActive = props.isPlaceholderActive,
106
+ disabled = props.disabled,
107
+ inputStylesProps = _objectWithoutProperties(props, _excluded);
108
+ var _useInputStyles = textInput.useInputStyles(_objectSpread({
109
+ disabled: disabled
110
+ }, inputStylesProps)),
99
111
  _useInputStyles2 = _slicedToArray(_useInputStyles, 2),
100
112
  boxProps = _useInputStyles2[0],
101
113
  inputStyles = _useInputStyles2[1];
@@ -104,7 +116,9 @@ function useSelectStyles(props) {
104
116
  // Prevent text going underneath the chevron icon
105
117
  paddingRight: theme$1.sizing.xxsmall +
106
118
  // size of chevron icon
107
- theme$1.spacing.medium * 2 // paddingX value
119
+ theme$1.spacing.medium * 2
120
+ }, isPlaceholderActive && !disabled && {
121
+ color: theme$1.color.foreground.placeholder
108
122
  })];
109
123
  }
110
124
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
+ var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
5
6
  var _objectSpread = require('@babel/runtime/helpers/objectSpread2');
6
7
  var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
7
8
  var react$1 = require('@emotion/react');
@@ -13,6 +14,7 @@ var theme = require('@spark-web/theme');
13
14
  var react = require('react');
14
15
  var jsxRuntime = require('@emotion/react/jsx-runtime');
15
16
 
17
+ var _excluded = ["isPlaceholderActive", "disabled"];
16
18
  var Select = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
17
19
  var data = _ref.data,
18
20
  defaultValue = _ref.defaultValue,
@@ -29,9 +31,14 @@ var Select = /*#__PURE__*/react.forwardRef(function (_ref, forwardedRef) {
29
31
  disabled = _useFieldContext2$.disabled,
30
32
  invalid = _useFieldContext2$.invalid,
31
33
  a11yProps = _useFieldContext2[1];
34
+ // Only applies when value is controlled and explicitly empty — the component
35
+ // cannot detect when the user selects an option in uncontrolled mode, so we
36
+ // only apply the muted style when value === '' (i.e. the caller controls it).
37
+ var isPlaceholderActive = placeholder !== undefined && value === '';
32
38
  var _useSelectStyles = useSelectStyles({
33
39
  disabled: disabled,
34
- invalid: invalid
40
+ invalid: invalid,
41
+ isPlaceholderActive: isPlaceholderActive
35
42
  }),
36
43
  _useSelectStyles2 = _slicedToArray(_useSelectStyles, 2),
37
44
  boxProps = _useSelectStyles2[0],
@@ -95,7 +102,12 @@ var Indicator = function Indicator() {
95
102
  });
96
103
  };
97
104
  function useSelectStyles(props) {
98
- var _useInputStyles = textInput.useInputStyles(props),
105
+ var isPlaceholderActive = props.isPlaceholderActive,
106
+ disabled = props.disabled,
107
+ inputStylesProps = _objectWithoutProperties(props, _excluded);
108
+ var _useInputStyles = textInput.useInputStyles(_objectSpread({
109
+ disabled: disabled
110
+ }, inputStylesProps)),
99
111
  _useInputStyles2 = _slicedToArray(_useInputStyles, 2),
100
112
  boxProps = _useInputStyles2[0],
101
113
  inputStyles = _useInputStyles2[1];
@@ -104,7 +116,9 @@ function useSelectStyles(props) {
104
116
  // Prevent text going underneath the chevron icon
105
117
  paddingRight: theme$1.sizing.xxsmall +
106
118
  // size of chevron icon
107
- theme$1.spacing.medium * 2 // paddingX value
119
+ theme$1.spacing.medium * 2
120
+ }, isPlaceholderActive && !disabled && {
121
+ color: theme$1.color.foreground.placeholder
108
122
  })];
109
123
  }
110
124
 
@@ -1,3 +1,4 @@
1
+ import _objectWithoutProperties from '@babel/runtime/helpers/esm/objectWithoutProperties';
1
2
  import _objectSpread from '@babel/runtime/helpers/esm/objectSpread2';
2
3
  import _slicedToArray from '@babel/runtime/helpers/esm/slicedToArray';
3
4
  import { css } from '@emotion/react';
@@ -9,6 +10,7 @@ import { useTheme } from '@spark-web/theme';
9
10
  import { forwardRef, useCallback } from 'react';
10
11
  import { jsx, jsxs } from '@emotion/react/jsx-runtime';
11
12
 
13
+ var _excluded = ["isPlaceholderActive", "disabled"];
12
14
  var Select = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
13
15
  var data = _ref.data,
14
16
  defaultValue = _ref.defaultValue,
@@ -25,9 +27,14 @@ var Select = /*#__PURE__*/forwardRef(function (_ref, forwardedRef) {
25
27
  disabled = _useFieldContext2$.disabled,
26
28
  invalid = _useFieldContext2$.invalid,
27
29
  a11yProps = _useFieldContext2[1];
30
+ // Only applies when value is controlled and explicitly empty — the component
31
+ // cannot detect when the user selects an option in uncontrolled mode, so we
32
+ // only apply the muted style when value === '' (i.e. the caller controls it).
33
+ var isPlaceholderActive = placeholder !== undefined && value === '';
28
34
  var _useSelectStyles = useSelectStyles({
29
35
  disabled: disabled,
30
- invalid: invalid
36
+ invalid: invalid,
37
+ isPlaceholderActive: isPlaceholderActive
31
38
  }),
32
39
  _useSelectStyles2 = _slicedToArray(_useSelectStyles, 2),
33
40
  boxProps = _useSelectStyles2[0],
@@ -91,7 +98,12 @@ var Indicator = function Indicator() {
91
98
  });
92
99
  };
93
100
  function useSelectStyles(props) {
94
- var _useInputStyles = useInputStyles(props),
101
+ var isPlaceholderActive = props.isPlaceholderActive,
102
+ disabled = props.disabled,
103
+ inputStylesProps = _objectWithoutProperties(props, _excluded);
104
+ var _useInputStyles = useInputStyles(_objectSpread({
105
+ disabled: disabled
106
+ }, inputStylesProps)),
95
107
  _useInputStyles2 = _slicedToArray(_useInputStyles, 2),
96
108
  boxProps = _useInputStyles2[0],
97
109
  inputStyles = _useInputStyles2[1];
@@ -100,7 +112,9 @@ function useSelectStyles(props) {
100
112
  // Prevent text going underneath the chevron icon
101
113
  paddingRight: theme.sizing.xxsmall +
102
114
  // size of chevron icon
103
- theme.spacing.medium * 2 // paddingX value
115
+ theme.spacing.medium * 2
116
+ }, isPlaceholderActive && !disabled && {
117
+ color: theme.color.foreground.placeholder
104
118
  })];
105
119
  }
106
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spark-web/select",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
4
4
  "homepage": "https://github.com/brighte-labs/spark-web#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,25 +11,26 @@
11
11
  "module": "dist/spark-web-select.esm.js",
12
12
  "files": [
13
13
  "CHANGELOG.md",
14
+ "CLAUDE.md",
14
15
  "dist",
15
16
  "README.md"
16
17
  ],
17
18
  "dependencies": {
18
19
  "@babel/runtime": "^7.25.0",
19
20
  "@emotion/react": "^11.14.0",
20
- "@spark-web/box": "^6.0.0",
21
+ "@spark-web/box": "^6.0.1",
21
22
  "@spark-web/icon": "^5.1.0",
22
- "@spark-web/text-input": "^6.0.0",
23
+ "@spark-web/text-input": "^6.0.2",
23
24
  "@spark-web/theme": "^5.13.0"
24
25
  },
25
26
  "devDependencies": {
26
- "@spark-web/field": "^5.3.0",
27
+ "@spark-web/field": "^5.3.2",
27
28
  "@spark-web/utils": "^5.1.0",
28
29
  "@types/react": "^19.1.0",
29
30
  "react": "^19.1.0"
30
31
  },
31
32
  "peerDependencies": {
32
- "@spark-web/field": "^5.3.0",
33
+ "@spark-web/field": "^5.3.2",
33
34
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
34
35
  },
35
36
  "engines": {