@inglorious/ui 7.2.1 → 7.2.3

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,10 +1,10 @@
1
- .iw-input-field {
2
- display: flex;
1
+ .iw-input {
2
+ display: inline-flex;
3
3
  flex-direction: column;
4
4
  gap: var(--iw-space-1);
5
5
  font-family: var(--iw-font-family-sans);
6
6
 
7
- &.iw-input-full-width .iw-input {
7
+ &.iw-input-full-width {
8
8
  width: 100%;
9
9
  }
10
10
 
@@ -12,6 +12,7 @@
12
12
  font-size: var(--iw-font-size-sm);
13
13
  font-weight: var(--iw-font-weight-normal);
14
14
  color: var(--iw-color-text);
15
+ line-height: var(--iw-line-height-normal);
15
16
  }
16
17
 
17
18
  .iw-input-required {
@@ -23,90 +24,76 @@
23
24
  position: relative;
24
25
  display: flex;
25
26
  align-items: center;
26
- }
27
-
28
- .iw-input {
29
- height: 2.25rem;
27
+ gap: var(--iw-space-2);
30
28
  padding: 0 var(--iw-space-3);
31
- font-family: inherit;
32
- font-size: var(--iw-font-size-base);
33
- line-height: 1.2;
34
- color: var(--iw-input-text);
35
- background-color: var(--iw-input-bg);
36
29
  border: 1px solid var(--iw-input-border);
37
30
  border-radius: var(--iw-input-radius);
38
- outline: none;
39
- transition: all var(--iw-transition-fast);
31
+ transition:
32
+ border-color var(--iw-transition-fast),
33
+ box-shadow var(--iw-transition-fast),
34
+ background-color var(--iw-transition-fast);
40
35
 
41
- &::placeholder {
42
- color: var(--iw-input-text-placeholder);
43
- }
44
-
45
- &:hover:not(:disabled) {
36
+ &:hover:not(:has(.iw-input-control:disabled)) {
46
37
  background-color: var(--iw-input-bg-hover);
47
38
  border-color: var(--iw-input-border-hover);
48
39
  }
49
40
 
50
- &:focus:not(:disabled) {
41
+ &:focus-within:not(:has(.iw-input-control:disabled)) {
51
42
  background-color: var(--iw-input-bg-focus);
52
43
  border-color: var(--iw-input-border-focus);
53
44
  box-shadow: var(--iw-input-shadow-focus);
54
45
  }
55
46
 
56
- &:disabled {
47
+ &:has(.iw-input-control-disabled) {
57
48
  opacity: 0.5;
58
49
  cursor: not-allowed;
59
50
  }
60
51
 
61
- &.iw-input-error {
52
+ &:has(.iw-input-control-error) {
62
53
  border-color: var(--iw-color-error);
63
54
 
64
- &:focus {
55
+ &:focus-within {
65
56
  border-color: var(--iw-color-error);
66
57
  box-shadow: 0 0 0 1px var(--iw-color-error);
67
58
  }
68
59
  }
69
60
 
70
- &.iw-input-number {
71
- text-align: right;
72
- }
61
+ .iw-input-control {
62
+ /* Fills the frame, which is now the box. */
63
+ flex: 1;
64
+ min-width: 0;
65
+ font-family: inherit;
66
+ line-height: 1.2;
67
+ color: var(--iw-input-text);
68
+ background-color: transparent;
69
+ border: none;
70
+ outline: none;
71
+
72
+ &::placeholder {
73
+ color: var(--iw-input-text-placeholder);
74
+ }
73
75
 
74
- &.iw-input-sm {
75
- height: 2rem;
76
- padding: 0 var(--iw-space-2);
77
- font-size: var(--iw-font-size-sm);
78
- }
76
+ &.iw-input-number {
77
+ text-align: right;
78
+ }
79
79
 
80
- &.iw-input-lg {
81
- height: 2.75rem;
82
- padding: 0 var(--iw-space-4);
83
- font-size: var(--iw-font-size-lg);
84
- }
85
- }
80
+ &.iw-input-sm {
81
+ height: 2rem;
82
+ font-size: var(--iw-font-size-sm);
83
+ }
86
84
 
87
- .iw-input-icon {
88
- position: absolute;
89
- left: var(--iw-space-3);
90
- display: flex;
91
- align-items: center;
92
- justify-content: center;
93
- color: var(--iw-color-text-muted);
94
- pointer-events: none;
85
+ &.iw-input-md {
86
+ height: 2.25rem;
87
+ font-size: var(--iw-font-size-base);
88
+ }
95
89
 
96
- &.iw-input-icon-after {
97
- left: auto;
98
- right: var(--iw-space-3);
90
+ &.iw-input-lg {
91
+ height: 2.75rem;
92
+ font-size: var(--iw-font-size-lg);
93
+ }
99
94
  }
100
95
  }
101
96
 
102
- &.iw-input-has-icon .iw-input {
103
- padding-left: calc(var(--iw-space-3) * 2 + 1em);
104
- }
105
-
106
- &.iw-input-has-icon-after .iw-input {
107
- padding-right: calc(var(--iw-space-3) * 2 + 1em);
108
- }
109
-
110
97
  .iw-input-hint,
111
98
  .iw-input-error-message {
112
99
  font-size: var(--iw-font-size-sm);
@@ -6,7 +6,9 @@
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
  import { ref } from "@inglorious/web/directives/ref"
9
+ import { when } from "@inglorious/web/directives/when"
9
10
 
11
+ import { Icon } from "../../data-display/icon/index.js"
10
12
  import { applyElementProps } from "../../shared/applyElementProps.js"
11
13
 
12
14
  /**
@@ -19,100 +21,122 @@ import { applyElementProps } from "../../shared/applyElementProps.js"
19
21
  * @param {InputProps} props
20
22
  * @returns {TemplateResult}
21
23
  */
22
- export function render(props) {
23
- const {
24
- id,
25
- type, // eslint-disable-line no-unused-vars
26
- name = "",
27
- inputType = "text",
28
- value = "",
29
- placeholder = "",
30
- label,
31
- hint,
32
- error,
33
- size = "md",
34
- isDisabled = false,
35
- isReadOnly = false,
36
- isRequired = false,
37
- isFullWidth = false,
38
- icon,
39
- iconAfter,
40
- onChange,
41
- onBlur,
42
- onFocus,
43
- ...rest
44
- } = props
24
+ export const Input = {
25
+ render(props) {
26
+ return this.renderInput(props)
27
+ },
45
28
 
46
- const inputId = id || name
29
+ renderInput(props) {
30
+ const {
31
+ id,
32
+ type, // eslint-disable-line no-unused-vars
33
+ name = "",
34
+ label,
35
+ hint,
36
+ error,
37
+ isDisabled = false,
38
+ isRequired = false,
39
+ isFullWidth = false,
40
+ icon,
41
+ iconAfter,
42
+ } = props
47
43
 
48
- const wrapperClasses = {
49
- "iw-input-field": true,
50
- "iw-input-full-width": isFullWidth,
51
- "iw-input-disabled": isDisabled,
52
- "iw-input-has-error": !!error,
53
- "iw-input-has-icon": !!icon,
54
- "iw-input-has-icon-after": !!iconAfter,
55
- }
44
+ const controlId = id || name
45
+ const messageId = (error || hint) && id ? `${id}-message` : ""
56
46
 
57
- const inputClasses = {
58
- "iw-input": true,
59
- [`iw-input-${size}`]: size !== "md",
60
- "iw-input-error": !!error,
61
- "iw-input-number": inputType === "number",
62
- }
47
+ const inputClasses = {
48
+ "iw-input": true,
49
+ "iw-input-full-width": isFullWidth,
50
+ "iw-input-disabled": isDisabled,
51
+ "iw-input-has-error": !!error,
52
+ "iw-input-has-icon": !!icon,
53
+ "iw-input-has-icon-after": !!iconAfter,
54
+ }
63
55
 
64
- return html`
65
- <div class=${classMap(wrapperClasses)}>
66
- ${
67
- label
68
- ? html`
69
- <label for=${inputId} class="iw-input-label">
70
- ${label}
71
- ${
72
- isRequired
73
- ? html`<span class="iw-input-required">*</span>`
74
- : null
75
- }
76
- </label>
77
- `
78
- : null
79
- }
56
+ return html`
57
+ <div class=${classMap(inputClasses)}>
58
+ ${when(
59
+ label,
60
+ () => html`
61
+ <label for=${controlId} class="iw-input-label">
62
+ ${label}
63
+ ${when(
64
+ isRequired,
65
+ () => html`<span class="iw-input-required">*</span>`,
66
+ )}
67
+ </label>
68
+ `,
69
+ )}
70
+ ${this.renderControl?.(props)}
71
+ ${when(
72
+ error,
73
+ () =>
74
+ html`<span id=${messageId || ""} class="iw-input-error-message"
75
+ >${error}</span
76
+ >`,
77
+ )}
78
+ ${when(
79
+ !error && hint,
80
+ () =>
81
+ html`<span id=${messageId || ""} class="iw-input-hint"
82
+ >${hint}</span
83
+ >`,
84
+ )}
85
+ </div>
86
+ `
87
+ },
80
88
 
81
- <div class="iw-input-frame">
82
- ${icon ? html`<span class="iw-input-icon">${icon}</span>` : null}
89
+ renderControl(props) {
90
+ const {
91
+ id,
92
+ type, // eslint-disable-line no-unused-vars
93
+ name = "",
94
+ inputType = "text",
95
+ value = "",
96
+ placeholder = "",
97
+ error,
98
+ size = "md",
99
+ isDisabled = false,
100
+ isReadOnly = false,
101
+ isRequired = false,
102
+ icon,
103
+ iconAfter,
104
+ onChange,
105
+ onBlur,
106
+ onFocus,
107
+ ...rest
108
+ } = props
83
109
 
84
- <input
85
- id=${inputId}
86
- name=${name}
87
- type=${inputType}
88
- .value=${value}
89
- placeholder=${placeholder}
90
- ?disabled=${isDisabled}
91
- ?readonly=${isReadOnly}
92
- ?required=${isRequired}
93
- class=${classMap(inputClasses)}
94
- @input=${(event) => onChange?.(event.target.value)}
95
- @blur=${onBlur}
96
- @focus=${onFocus}
97
- ${ref((element) => applyElementProps(element, rest))}
98
- />
110
+ const controlId = id || name
99
111
 
100
- ${
101
- iconAfter
102
- ? html`<span class="iw-input-icon iw-input-icon-after"
103
- >${iconAfter}</span
104
- >`
105
- : null
106
- }
107
- </div>
112
+ const controlClasses = {
113
+ "iw-input-control": true,
114
+ [`iw-input-${size}`]: true,
115
+ "iw-input-control-disabled": isDisabled,
116
+ "iw-input-control-error": !!error,
117
+ "iw-input-number": inputType === "number",
118
+ }
119
+
120
+ return html`<div class="iw-input-frame">
121
+ ${when(icon, () => Icon.render({ children: icon, color: "var(--iw-color-text-muted)", size }))}
122
+
123
+ <input
124
+ id=${controlId}
125
+ name=${name}
126
+ type=${inputType}
127
+ .value=${value}
128
+ placeholder=${placeholder}
129
+ ?disabled=${isDisabled}
130
+ ?readonly=${isReadOnly}
131
+ ?required=${isRequired}
132
+ class=${classMap(controlClasses)}
133
+ @input=${(event) => onChange?.(event.target.value)}
134
+ @blur=${onBlur}
135
+ @focus=${onFocus}
136
+ ${ref((element) => applyElementProps(element, rest))}
137
+ />
108
138
 
109
- ${
110
- error
111
- ? html`<span class="iw-input-error-message">${error}</span>`
112
- : hint
113
- ? html`<span class="iw-input-hint">${hint}</span>`
114
- : null
115
- }
116
- </div>
117
- `
139
+ ${when(iconAfter, () => Icon.render({ children: iconAfter, color: "var(--iw-color-text-muted)", size }))}
140
+ </div>`
141
+ },
118
142
  }
@@ -1,3 +1,3 @@
1
- import * as renderers from "./template.js"
1
+ import { Select as renderers } from "./template.js"
2
2
 
3
3
  export const Select = { ...renderers }
@@ -1,5 +1,5 @@
1
1
  import { createRender } from "../../stories/notifyStory.js"
2
- import { Select } from "./index.js"
2
+ import { Select } from "."
3
3
 
4
4
  export default {
5
5
  title: "Controls/Select",
@@ -34,7 +34,8 @@ export default {
34
34
  parameters: {
35
35
  docs: {
36
36
  description: {
37
- component: "Select field control.",
37
+ component:
38
+ "Select field control with labels, hints, errors, and semantic states.",
38
39
  },
39
40
  },
40
41
  },
@@ -42,9 +43,11 @@ export default {
42
43
 
43
44
  export const Default = {}
44
45
  Default.args = {
46
+ label: "Animals",
45
47
  name: "animals",
46
48
  options: ["Cat", "Dog", "Seal"],
47
49
  value: "",
50
+ error: "",
48
51
  size: "md",
49
52
  isDisabled: false,
50
53
  isFullWidth: false,
@@ -60,10 +63,33 @@ ComplexOptions.args = {
60
63
  ],
61
64
  }
62
65
 
66
+ export const WithHint = {}
67
+ WithHint.args = {
68
+ ...Default.args,
69
+ hint: "Please select an animal",
70
+ }
71
+
72
+ export const WithError = {}
73
+ WithError.args = {
74
+ ...Default.args,
75
+ error: "You cannot select this one!",
76
+ }
77
+
78
+ export const WithHintAndError = {}
79
+ WithHintAndError.args = {
80
+ ...WithHint.args,
81
+ error: "You cannot select this one!",
82
+ }
83
+
84
+ export const Required = {}
85
+ Required.args = {
86
+ ...Default.args,
87
+ isRequired: true,
88
+ }
89
+
63
90
  export const Disabled = {}
64
91
  Disabled.args = {
65
92
  ...Default.args,
66
- value: "Cat",
67
93
  isDisabled: true,
68
94
  }
69
95
 
@@ -81,6 +107,12 @@ Small.args = {
81
107
  size: "sm",
82
108
  }
83
109
 
110
+ export const Medium = {}
111
+ Medium.args = {
112
+ ...Default.args,
113
+ size: "md",
114
+ }
115
+
84
116
  export const Large = {}
85
117
  Large.args = {
86
118
  ...Default.args,
@@ -1,88 +1,167 @@
1
1
  .iw-select {
2
- appearance: none;
2
+ display: inline-flex;
3
+ flex-direction: column;
4
+ gap: var(--iw-space-1);
3
5
  font-family: var(--iw-font-family-sans);
4
- font-size: var(--iw-font-size-sm);
5
- line-height: var(--iw-line-height-normal);
6
- height: 2.25rem;
7
- border: 1px solid var(--iw-input-border);
8
- border-radius: var(--iw-input-radius);
9
- padding: 0 var(--iw-space-10) 0 var(--iw-space-3);
10
- background: var(--iw-input-bg);
11
- background-image:
12
- linear-gradient(
13
- 45deg,
14
- transparent 50%,
15
- var(--iw-input-text, var(--iw-color-text)) 50%
16
- ),
17
- linear-gradient(
18
- 135deg,
19
- var(--iw-input-text, var(--iw-color-text)) 50%,
20
- transparent 50%
21
- );
22
- background-position:
23
- right calc(var(--iw-space-4) + 0.3125rem) center,
24
- right var(--iw-space-4) center;
25
- background-size:
26
- 0.375rem 0.375rem,
27
- 0.375rem 0.375rem;
28
- background-repeat: no-repeat;
29
- color: var(--iw-input-text);
30
- cursor: pointer;
31
- user-select: none;
32
- transition:
33
- border-color var(--iw-transition-fast),
34
- box-shadow var(--iw-transition-fast),
35
- background-color var(--iw-transition-fast);
36
6
 
37
7
  &.iw-select-full-width {
38
8
  width: 100%;
39
9
  }
40
10
 
41
- &:hover {
42
- border-color: var(--iw-input-border-hover);
43
- background: var(--iw-input-bg-hover);
11
+ .iw-select-label {
12
+ font-size: var(--iw-font-size-sm);
13
+ font-weight: var(--iw-font-weight-normal);
14
+ color: var(--iw-color-text);
15
+ line-height: var(--iw-line-height-normal);
44
16
  }
45
17
 
46
- &:disabled {
47
- opacity: 0.6;
48
- cursor: not-allowed;
18
+ .iw-select-required {
19
+ color: var(--iw-color-error);
20
+ margin-left: var(--iw-space-1);
49
21
  }
50
22
 
51
- &[multiple] {
52
- appearance: auto;
53
- background-image: none;
23
+ .iw-select-hint,
24
+ .iw-select-error-message {
25
+ font-size: var(--iw-font-size-sm);
26
+ line-height: var(--iw-line-height-normal);
54
27
  }
55
28
 
56
- .iw-select-option {
29
+ .iw-select-hint {
30
+ color: var(--iw-color-text-muted);
31
+ }
32
+
33
+ .iw-select-error-message {
34
+ color: var(--iw-color-error);
35
+ }
36
+
37
+ .iw-select-frame {
38
+ display: flex;
39
+ align-items: center;
40
+ /* The gap spaces the text from the arrow, the same way it spaces an
41
+ Input's text from its icons. */
42
+ gap: var(--iw-space-2);
43
+ padding: 0 var(--iw-space-3);
44
+ border: 1px solid var(--iw-input-border);
45
+ border-radius: var(--iw-input-radius);
46
+ color: var(--iw-input-text);
47
+ background-color: var(--iw-input-bg);
48
+ cursor: pointer;
57
49
  user-select: none;
58
- padding: var(--iw-space-2) var(--iw-space-3);
50
+ transition:
51
+ border-color var(--iw-transition-fast),
52
+ box-shadow var(--iw-transition-fast),
53
+ background-color var(--iw-transition-fast);
59
54
 
60
- &:focused {
61
- background: var(--iw-color-bg-secondary);
55
+ &:hover:not(:has(.iw-select-control:disabled)) {
56
+ background-color: var(--iw-input-bg-hover);
57
+ border-color: var(--iw-input-border-hover);
62
58
  }
63
59
 
64
- &.iw-select-option-selected {
65
- background: color-mix(
66
- in srgb,
67
- var(--iw-color-primary) 16%,
68
- var(--iw-color-bg) 84%
69
- );
60
+ &:focus-within:not(:has(.iw-select-control:disabled)) {
61
+ background-color: var(--iw-input-bg-focus);
62
+ border-color: var(--iw-input-border-focus);
63
+ box-shadow: var(--iw-input-shadow-focus);
70
64
  }
71
- }
72
65
 
73
- &.iw-select-sm {
74
- height: 2rem;
75
- padding: 0 var(--iw-space-10) 0 var(--iw-space-2);
76
- background-position:
77
- right calc(var(--iw-space-3) + 0.3125rem) center,
78
- right var(--iw-space-3) center;
79
- }
66
+ &:has(.iw-select-control-disabled) {
67
+ opacity: 0.5;
68
+ cursor: not-allowed;
69
+ }
70
+
71
+ &:has(.iw-select-control-error) {
72
+ border-color: var(--iw-color-error);
73
+
74
+ &:focus-within {
75
+ border-color: var(--iw-color-error);
76
+ box-shadow: 0 0 0 1px var(--iw-color-error);
77
+ }
78
+ }
79
+
80
+ /* The control is focusable so it can be opened from the keyboard. */
81
+ &:focus-visible {
82
+ outline: none;
83
+ border-color: var(--iw-input-border-focus);
84
+ box-shadow: var(--iw-input-shadow-focus);
85
+ }
86
+
87
+ .iw-select-control {
88
+ appearance: none;
89
+ flex: 1;
90
+ min-width: 0;
91
+ font-family: inherit;
92
+ line-height: 1.2;
93
+ background-color: transparent;
94
+ border: none;
95
+ outline: none;
80
96
 
81
- &.iw-select-lg {
82
- height: 2.75rem;
83
- padding: 0 var(--iw-space-12) 0 var(--iw-space-4);
84
- background-position:
85
- right calc(var(--iw-space-5) + 0.3125rem) center,
86
- right var(--iw-space-5) center;
97
+ background-color: var(--iw-input-bg);
98
+ color: var(--iw-input-text);
99
+ cursor: pointer;
100
+ user-select: none;
101
+ transition:
102
+ border-color var(--iw-transition-fast),
103
+ box-shadow var(--iw-transition-fast),
104
+ background-color var(--iw-transition-fast);
105
+
106
+ &[multiple] {
107
+ appearance: auto;
108
+ background-image: none;
109
+ }
110
+
111
+ &.iw-select-sm {
112
+ height: 2rem;
113
+ font-size: var(--iw-font-size-sm);
114
+ }
115
+
116
+ &.iw-select-md {
117
+ height: 2.25rem;
118
+ font-size: var(--iw-font-size-base);
119
+ }
120
+
121
+ &.iw-select-lg {
122
+ height: 2.75rem;
123
+ font-size: var(--iw-font-size-lg);
124
+ }
125
+
126
+ .iw-select-option {
127
+ user-select: none;
128
+ padding: var(--iw-space-2) var(--iw-space-3);
129
+
130
+ &:focused {
131
+ background-color: var(--iw-color-bg-secondary);
132
+ }
133
+
134
+ &.iw-select-option-selected {
135
+ background-color: color-mix(
136
+ in srgb,
137
+ var(--iw-color-primary) 16%,
138
+ var(--iw-color-bg) 84%
139
+ );
140
+ }
141
+ }
142
+ }
143
+
144
+ .iw-select-arrow {
145
+ width: 1.25rem;
146
+ height: 1.25rem;
147
+ display: inline-flex;
148
+ align-items: center;
149
+ justify-content: center;
150
+ font-size: var(--iw-font-size-sm);
151
+ line-height: 1;
152
+ /* A flex child, like Input's icons. Absolutely positioning it needed a
153
+ positioning context on the frame and a reserved padding strip so the
154
+ text would not run underneath it; the row's gap does both. */
155
+ flex: none;
156
+ /* Decorative. A popup only opens from a trusted gesture on the select
157
+ itself, so the arrow cannot open it; leaving clicks to pass through
158
+ keeps the strip from swallowing them. */
159
+ pointer-events: none;
160
+ color: var(--iw-color-text-muted);
161
+ }
87
162
  }
88
163
  }
164
+
165
+ .iw-select-frame:has(.iw-select-control:disabled) .iw-select-arrow {
166
+ opacity: 0.6;
167
+ }