@labcat2020/rocketship-svelte 0.1.0 → 0.3.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/README.md +117 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/package.json +22 -3
- package/src/components/Accordion.svelte +89 -0
- package/src/components/Alert.svelte +129 -0
- package/src/components/Button.svelte +187 -0
- package/src/components/Card.svelte +124 -0
- package/src/components/Container.svelte +95 -0
- package/src/components/Disclosure.svelte +179 -0
- package/src/components/Field.svelte +183 -0
- package/src/components/Figure.svelte +128 -0
- package/src/components/Grid.svelte +62 -0
- package/src/components/Input.svelte +138 -0
- package/src/components/Label.svelte +92 -0
- package/src/components/Select.svelte +235 -0
- package/src/components/Table.svelte +193 -0
- package/src/components/Textarea.svelte +138 -0
- package/src/components/Typography.svelte +205 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements'
|
|
4
|
+
|
|
5
|
+
export type GridColumns = 2 | 3 | 4
|
|
6
|
+
export type GridTag = 'div' | 'ul' | 'ol'
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
as?: GridTag
|
|
10
|
+
columns?: GridColumns
|
|
11
|
+
class?: string
|
|
12
|
+
children?: Snippet
|
|
13
|
+
} & HTMLAttributes<HTMLElement>
|
|
14
|
+
|
|
15
|
+
let { as = 'div', columns = 2, class: className = '', children, ...attrs }: Props = $props()
|
|
16
|
+
|
|
17
|
+
const classes = $derived(
|
|
18
|
+
[
|
|
19
|
+
'rs-grid',
|
|
20
|
+
columns === 3 ? 'rs-grid--3-cols' : undefined,
|
|
21
|
+
columns === 4 ? 'rs-grid--4-cols' : undefined,
|
|
22
|
+
className,
|
|
23
|
+
]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(' '),
|
|
26
|
+
)
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<svelte:element this={as} {...attrs} class={classes}>
|
|
30
|
+
{@render children?.()}
|
|
31
|
+
</svelte:element>
|
|
32
|
+
|
|
33
|
+
<style lang="scss">
|
|
34
|
+
@use '@labcat2020/rocketship/styles/mixins' as *;
|
|
35
|
+
|
|
36
|
+
:global {
|
|
37
|
+
.rs-grid {
|
|
38
|
+
display: grid;
|
|
39
|
+
gap: var(--rs-grid-gap, var(--rs-space-4, 16px));
|
|
40
|
+
grid-template-columns: minmax(0, 1fr);
|
|
41
|
+
list-style: none;
|
|
42
|
+
margin: 0;
|
|
43
|
+
padding: 0;
|
|
44
|
+
|
|
45
|
+
@include container-query-up($rs-cq-sm) {
|
|
46
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&--3-cols {
|
|
50
|
+
@include container-query-up($rs-cq-md) {
|
|
51
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&--4-cols {
|
|
56
|
+
@include container-query-up($rs-cq-md) {
|
|
57
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements'
|
|
3
|
+
|
|
4
|
+
export type InputType = 'text' | 'email' | 'password' | 'number' | 'search' | 'url' | 'tel'
|
|
5
|
+
export type InputSize = 'sm' | 'md' | 'lg'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
type?: InputType
|
|
9
|
+
size?: InputSize
|
|
10
|
+
invalid?: boolean
|
|
11
|
+
class?: string
|
|
12
|
+
value?: string | number
|
|
13
|
+
} & Omit<HTMLInputAttributes, 'size' | 'type' | 'value'>
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
type = 'text',
|
|
17
|
+
size = 'md',
|
|
18
|
+
invalid = false,
|
|
19
|
+
class: className = '',
|
|
20
|
+
value = $bindable(''),
|
|
21
|
+
...attrs
|
|
22
|
+
}: Props = $props()
|
|
23
|
+
|
|
24
|
+
// Field passes its wiring (`id`, `aria-describedby`, `aria-invalid`, `required`, `disabled`) to
|
|
25
|
+
// the `children` snippet, which spreads it onto this control. An explicit attribute wins over the
|
|
26
|
+
// `invalid` prop, so the error state Field reports is the one the element announces and styles.
|
|
27
|
+
const ariaInvalid = $derived(attrs['aria-invalid'] ?? (invalid ? 'true' : undefined))
|
|
28
|
+
// Only the hard `true` state drives the styling; `spelling`/`grammar` are announced but not
|
|
29
|
+
// painted as an error, and an explicit `aria-invalid="false"` always wins over the prop.
|
|
30
|
+
const isInvalid = $derived(ariaInvalid === true || ariaInvalid === 'true')
|
|
31
|
+
|
|
32
|
+
const classes = $derived(
|
|
33
|
+
['rs-input', `rs-input--${size}`, isInvalid ? 'rs-input--invalid' : undefined, className]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(' '),
|
|
36
|
+
)
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<input {...attrs} aria-invalid={ariaInvalid} bind:value class={classes} {type} />
|
|
40
|
+
|
|
41
|
+
<style lang="scss">
|
|
42
|
+
@use '@labcat2020/rocketship/styles/mixins' as *;
|
|
43
|
+
|
|
44
|
+
:global {
|
|
45
|
+
.rs-input {
|
|
46
|
+
// Size locals live on the block so a size modifier only reassigns them; `--rs-input-font-size`
|
|
47
|
+
// stays unset here so a consumer can override one control without touching the others.
|
|
48
|
+
--rs-input-font-size-sm: var(--rs-font-size-sm, #{to-rem(14)});
|
|
49
|
+
--rs-input-font-size-md: var(--rs-font-size-md, #{to-rem(16)});
|
|
50
|
+
--rs-input-font-size-lg: var(--rs-font-size-lg, #{to-rem(18)});
|
|
51
|
+
--rs-input-min-height-sm: 34px;
|
|
52
|
+
--rs-input-min-height-md: 40px;
|
|
53
|
+
--rs-input-min-height-lg: 48px;
|
|
54
|
+
--rs-input-padding-inline-sm: var(--rs-space-2, 8px);
|
|
55
|
+
--rs-input-padding-inline-md: var(--rs-space-3, 12px);
|
|
56
|
+
--rs-input-padding-inline-lg: var(--rs-space-4, 16px);
|
|
57
|
+
background: var(--rs-input-bg, var(--rs-color-surface, #ffffff));
|
|
58
|
+
border: var(--rs-input-border-width, var(--rs-border-width-sm, 1px)) solid
|
|
59
|
+
var(--rs-input-border-color, var(--rs-color-border, #c8d2eb));
|
|
60
|
+
border-radius: var(--rs-input-border-radius, var(--rs-radius-sm, 6px));
|
|
61
|
+
box-sizing: border-box;
|
|
62
|
+
color: var(--rs-input-color, var(--rs-color-fg, #131a2f));
|
|
63
|
+
display: block;
|
|
64
|
+
font-family: var(
|
|
65
|
+
--rs-font-family-sans,
|
|
66
|
+
'Inter',
|
|
67
|
+
'Segoe UI',
|
|
68
|
+
Roboto,
|
|
69
|
+
Helvetica,
|
|
70
|
+
Arial,
|
|
71
|
+
sans-serif
|
|
72
|
+
);
|
|
73
|
+
font-size: var(--rs-input-font-size, var(--rs-input-font-size-md));
|
|
74
|
+
line-height: var(--rs-line-height-base, 1.45);
|
|
75
|
+
min-height: var(--rs-input-min-height, var(--rs-input-min-height-md));
|
|
76
|
+
padding-block: var(--rs-input-padding-block, var(--rs-space-2, 8px));
|
|
77
|
+
padding-inline: var(--rs-input-padding-inline, var(--rs-input-padding-inline-md));
|
|
78
|
+
transition:
|
|
79
|
+
background-color var(--rs-transition-fast, 140ms) ease,
|
|
80
|
+
border-color var(--rs-transition-fast, 140ms) ease;
|
|
81
|
+
width: 100%;
|
|
82
|
+
|
|
83
|
+
&::placeholder {
|
|
84
|
+
color: var(--rs-input-placeholder-color, var(--rs-color-muted, #4f5b7d));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
&:focus-visible {
|
|
88
|
+
border-color: var(--rs-input-focus-border-color, var(--rs-color-accent, #2f5bdb));
|
|
89
|
+
outline: 2px solid var(--rs-input-focus-ring-color, var(--rs-color-focus-ring, #296dff));
|
|
90
|
+
outline-offset: 2px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&:disabled {
|
|
94
|
+
cursor: not-allowed;
|
|
95
|
+
opacity: 0.55;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&--sm {
|
|
99
|
+
--rs-input-font-size: var(--rs-input-font-size-sm);
|
|
100
|
+
--rs-input-min-height: var(--rs-input-min-height-sm);
|
|
101
|
+
--rs-input-padding-inline: var(--rs-input-padding-inline-sm);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&--md {
|
|
105
|
+
--rs-input-font-size: var(--rs-input-font-size-md);
|
|
106
|
+
--rs-input-min-height: var(--rs-input-min-height-md);
|
|
107
|
+
--rs-input-padding-inline: var(--rs-input-padding-inline-md);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
&--lg {
|
|
111
|
+
--rs-input-font-size: var(--rs-input-font-size-lg);
|
|
112
|
+
--rs-input-min-height: var(--rs-input-min-height-lg);
|
|
113
|
+
--rs-input-padding-inline: var(--rs-input-padding-inline-lg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The error state owns a coordinated colour set, so the modifier reassigns the locals the base
|
|
117
|
+
// rule already reads instead of restating background, border and colour.
|
|
118
|
+
&--invalid {
|
|
119
|
+
--rs-input-bg: var(--rs-color-danger-bg, #ffeef0);
|
|
120
|
+
--rs-input-border-color: var(--rs-color-danger-border, #f0a8b0);
|
|
121
|
+
--rs-input-color: var(--rs-color-danger-fg, #3d0a08);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@include container-query-up($rs-cq-sm) {
|
|
125
|
+
--rs-input-font-size-sm: var(--rs-font-size-md, #{to-rem(16)});
|
|
126
|
+
--rs-input-font-size-md: var(--rs-font-size-lg, #{to-rem(18)});
|
|
127
|
+
--rs-input-font-size-lg: var(--rs-font-size-xl, #{to-rem(20)});
|
|
128
|
+
--rs-input-min-height-sm: 40px;
|
|
129
|
+
--rs-input-min-height-md: 44px;
|
|
130
|
+
--rs-input-min-height-lg: 52px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@media (prefers-reduced-motion: reduce) {
|
|
134
|
+
transition: none;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { HTMLLabelAttributes } from 'svelte/elements'
|
|
4
|
+
|
|
5
|
+
export type LabelTag = 'label' | 'span' | 'div'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
as?: LabelTag
|
|
9
|
+
required?: boolean
|
|
10
|
+
optional?: boolean
|
|
11
|
+
requiredText?: string
|
|
12
|
+
optionalText?: string
|
|
13
|
+
class?: string
|
|
14
|
+
children?: Snippet
|
|
15
|
+
} & HTMLLabelAttributes
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
as = 'label',
|
|
19
|
+
required = false,
|
|
20
|
+
optional = false,
|
|
21
|
+
requiredText = '(required)',
|
|
22
|
+
optionalText = '(optional)',
|
|
23
|
+
class: className = '',
|
|
24
|
+
for: htmlFor,
|
|
25
|
+
children,
|
|
26
|
+
...attrs
|
|
27
|
+
}: Props = $props()
|
|
28
|
+
|
|
29
|
+
const classes = $derived(['rs-label', className].filter(Boolean).join(' '))
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<svelte:element this={as} {...attrs} class={classes} for={as === 'label' ? htmlFor : undefined}>
|
|
33
|
+
{@render children?.()}
|
|
34
|
+
{#if required}
|
|
35
|
+
<span class="rs-label__required" aria-hidden="true">*</span>
|
|
36
|
+
<span class="rs-label__sr-only">{requiredText}</span>
|
|
37
|
+
{:else if optional}
|
|
38
|
+
<span class="rs-label__optional">{optionalText}</span>
|
|
39
|
+
{/if}
|
|
40
|
+
</svelte:element>
|
|
41
|
+
|
|
42
|
+
<style lang="scss">
|
|
43
|
+
@use '@labcat2020/rocketship/styles/mixins' as *;
|
|
44
|
+
|
|
45
|
+
:global {
|
|
46
|
+
.rs-label {
|
|
47
|
+
align-items: baseline;
|
|
48
|
+
color: var(--rs-label-color, var(--rs-color-fg, #131a2f));
|
|
49
|
+
display: flex;
|
|
50
|
+
font-family: var(
|
|
51
|
+
--rs-font-family-sans,
|
|
52
|
+
'Inter',
|
|
53
|
+
'Segoe UI',
|
|
54
|
+
Roboto,
|
|
55
|
+
Helvetica,
|
|
56
|
+
Arial,
|
|
57
|
+
sans-serif
|
|
58
|
+
);
|
|
59
|
+
font-size: var(--rs-label-font-size, var(--rs-font-size-sm, #{to-rem(14)}));
|
|
60
|
+
font-weight: var(--rs-label-font-weight, var(--rs-font-weight-medium, 500));
|
|
61
|
+
gap: var(--rs-label-gap, var(--rs-space-1, 4px));
|
|
62
|
+
line-height: var(--rs-label-line-height, var(--rs-line-height-base, 1.45));
|
|
63
|
+
|
|
64
|
+
@include container-query-up($rs-cq-sm) {
|
|
65
|
+
font-size: var(--rs-label-font-size, var(--rs-font-size-md, #{to-rem(16)}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&__required {
|
|
69
|
+
color: var(--rs-label-required-color, var(--rs-color-danger-accent, #c5221f));
|
|
70
|
+
font-weight: var(--rs-font-weight-regular, 400);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&__optional {
|
|
74
|
+
color: var(--rs-label-optional-color, var(--rs-color-muted, #4f5b7d));
|
|
75
|
+
font-weight: var(--rs-font-weight-regular, 400);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
&__sr-only {
|
|
79
|
+
border: 0;
|
|
80
|
+
clip: rect(0 0 0 0);
|
|
81
|
+
clip-path: inset(50%);
|
|
82
|
+
height: 1px;
|
|
83
|
+
margin: -1px;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
padding: 0;
|
|
86
|
+
position: absolute;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
width: 1px;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte'
|
|
3
|
+
import type { HTMLSelectAttributes } from 'svelte/elements'
|
|
4
|
+
|
|
5
|
+
export type SelectSize = 'sm' | 'md' | 'lg'
|
|
6
|
+
|
|
7
|
+
type Props = {
|
|
8
|
+
size?: SelectSize | number
|
|
9
|
+
invalid?: boolean
|
|
10
|
+
placeholder?: string
|
|
11
|
+
class?: string
|
|
12
|
+
value?: string
|
|
13
|
+
children?: Snippet
|
|
14
|
+
} & Omit<HTMLSelectAttributes, 'size' | 'value' | 'placeholder' | 'multiple'>
|
|
15
|
+
|
|
16
|
+
// `size` is a Rocketship variant, but the native attribute of the same name is a visible row
|
|
17
|
+
// count that renders a listbox, so a non-variant value is forwarded to the element untouched.
|
|
18
|
+
const isSelectSize = (candidate: SelectSize | number | undefined): candidate is SelectSize =>
|
|
19
|
+
candidate === 'sm' || candidate === 'md' || candidate === 'lg'
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
size = 'md',
|
|
23
|
+
invalid = false,
|
|
24
|
+
placeholder,
|
|
25
|
+
class: className = '',
|
|
26
|
+
value = $bindable(''),
|
|
27
|
+
children,
|
|
28
|
+
...attrs
|
|
29
|
+
}: Props = $props()
|
|
30
|
+
|
|
31
|
+
const variantSize = $derived(isSelectSize(size) ? size : 'md')
|
|
32
|
+
const nativeSize = $derived(isSelectSize(size) ? undefined : size)
|
|
33
|
+
|
|
34
|
+
// Field passes its wiring (`id`, `aria-describedby`, `aria-invalid`, `required`, `disabled`) to
|
|
35
|
+
// the `children` snippet, which spreads it onto this component and so onto the `<select>`. An
|
|
36
|
+
// explicit attribute wins over the `invalid` prop, so the state the element announces is the one
|
|
37
|
+
// it is styled with.
|
|
38
|
+
const ariaInvalid = $derived(attrs['aria-invalid'] ?? (invalid ? 'true' : undefined))
|
|
39
|
+
// Only the hard `true` state drives the styling; `spelling`/`grammar` are announced but not
|
|
40
|
+
// painted as an error, and an explicit `aria-invalid="false"` always wins over the prop.
|
|
41
|
+
const isInvalid = $derived(ariaInvalid === true || ariaInvalid === 'true')
|
|
42
|
+
|
|
43
|
+
const hasPlaceholder = $derived(Boolean(placeholder?.trim()))
|
|
44
|
+
// A numeric `size` turns the control into a listbox, where a dropdown chevron would be false.
|
|
45
|
+
const showIcon = $derived(nativeSize === undefined)
|
|
46
|
+
|
|
47
|
+
// The placeholder is a disabled, empty-valued option, so a user can never select it back once a
|
|
48
|
+
// real option is chosen. Rendering `selected` keeps static server output on the placeholder; after
|
|
49
|
+
// hydration the `bind:value` binding owns which option is selected.
|
|
50
|
+
const placeholderSelected = $derived(value === '')
|
|
51
|
+
|
|
52
|
+
const classes = $derived(
|
|
53
|
+
[
|
|
54
|
+
'rs-select',
|
|
55
|
+
`rs-select--${variantSize}`,
|
|
56
|
+
isInvalid ? 'rs-select--invalid' : undefined,
|
|
57
|
+
className,
|
|
58
|
+
]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join(' '),
|
|
61
|
+
)
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<span class={classes}>
|
|
65
|
+
<select
|
|
66
|
+
{...attrs}
|
|
67
|
+
aria-invalid={ariaInvalid}
|
|
68
|
+
bind:value
|
|
69
|
+
class="rs-select__control"
|
|
70
|
+
size={nativeSize}
|
|
71
|
+
>
|
|
72
|
+
{#if hasPlaceholder}
|
|
73
|
+
<option disabled selected={placeholderSelected} value="">{placeholder}</option>
|
|
74
|
+
{/if}
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
</select>
|
|
77
|
+
{#if showIcon}
|
|
78
|
+
<svg
|
|
79
|
+
aria-hidden="true"
|
|
80
|
+
class="rs-select__icon"
|
|
81
|
+
focusable="false"
|
|
82
|
+
height="16"
|
|
83
|
+
viewBox="0 0 16 16"
|
|
84
|
+
width="16"
|
|
85
|
+
>
|
|
86
|
+
<path
|
|
87
|
+
d="M4 6l4 4 4-4"
|
|
88
|
+
fill="none"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
stroke-linecap="round"
|
|
91
|
+
stroke-linejoin="round"
|
|
92
|
+
stroke-width="1.5"
|
|
93
|
+
/>
|
|
94
|
+
</svg>
|
|
95
|
+
{/if}
|
|
96
|
+
</span>
|
|
97
|
+
|
|
98
|
+
<style lang="scss">
|
|
99
|
+
@use '@labcat2020/rocketship/styles/mixins' as *;
|
|
100
|
+
|
|
101
|
+
:global {
|
|
102
|
+
.rs-select {
|
|
103
|
+
// Variant-owned locals: the control reads them, so a modifier swaps a coordinated set.
|
|
104
|
+
--rs-select-bg: var(--rs-color-surface, #ffffff);
|
|
105
|
+
--rs-select-border-color: var(--rs-color-border, #c8d2eb);
|
|
106
|
+
--rs-select-color: var(--rs-color-fg, #131a2f);
|
|
107
|
+
--rs-select-focus-ring-color: var(--rs-color-focus-ring, #296dff);
|
|
108
|
+
--rs-select-icon-color: var(--rs-color-muted, #4f5b7d);
|
|
109
|
+
--rs-select-invalid-bg: var(--rs-color-danger-bg, #ffeef0);
|
|
110
|
+
--rs-select-invalid-border-color: var(--rs-color-danger-border, #f0a8b0);
|
|
111
|
+
--rs-select-invalid-focus-ring-color: var(--rs-color-danger-accent, #c5221f);
|
|
112
|
+
// Size defaults live on the block; the per-size hooks stay unset here, so an adopter can
|
|
113
|
+
// override one control through `--rs-select-font-size` without touching the others.
|
|
114
|
+
--rs-select-font-size: var(--rs-select-font-size-md, var(--rs-font-size-md, #{to-rem(16)}));
|
|
115
|
+
--rs-select-min-height: var(--rs-select-min-height-md, 40px);
|
|
116
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-md, var(--rs-space-3, 12px));
|
|
117
|
+
display: block;
|
|
118
|
+
position: relative;
|
|
119
|
+
|
|
120
|
+
&--sm {
|
|
121
|
+
--rs-select-font-size: var(--rs-select-font-size-sm, var(--rs-font-size-sm, #{to-rem(14)}));
|
|
122
|
+
--rs-select-min-height: var(--rs-select-min-height-sm, 34px);
|
|
123
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-sm, var(--rs-space-2, 8px));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&--md {
|
|
127
|
+
--rs-select-font-size: var(--rs-select-font-size-md, var(--rs-font-size-md, #{to-rem(16)}));
|
|
128
|
+
--rs-select-min-height: var(--rs-select-min-height-md, 40px);
|
|
129
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-md, var(--rs-space-3, 12px));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&--lg {
|
|
133
|
+
--rs-select-font-size: var(--rs-select-font-size-lg, var(--rs-font-size-lg, #{to-rem(18)}));
|
|
134
|
+
--rs-select-min-height: var(--rs-select-min-height-lg, 48px);
|
|
135
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-lg, var(--rs-space-4, 16px));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Roomier touch targets once the component's own container is wide enough.
|
|
139
|
+
@include container-query-up($rs-cq-sm) {
|
|
140
|
+
--rs-select-min-height: var(--rs-select-min-height-md, 44px);
|
|
141
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-md, var(--rs-space-4, 16px));
|
|
142
|
+
|
|
143
|
+
&--sm {
|
|
144
|
+
--rs-select-min-height: var(--rs-select-min-height-sm, 40px);
|
|
145
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-sm, var(--rs-space-3, 12px));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&--lg {
|
|
149
|
+
--rs-select-min-height: var(--rs-select-min-height-lg, 52px);
|
|
150
|
+
--rs-select-padding-inline: var(--rs-select-padding-inline-lg, var(--rs-space-5, 20px));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
&__control {
|
|
155
|
+
appearance: none;
|
|
156
|
+
background: var(--rs-select-bg);
|
|
157
|
+
border: var(--rs-select-border-width, var(--rs-border-width-sm, 1px)) solid
|
|
158
|
+
var(--rs-select-border-color);
|
|
159
|
+
border-radius: var(--rs-select-border-radius, var(--rs-radius-sm, 6px));
|
|
160
|
+
box-sizing: border-box;
|
|
161
|
+
color: var(--rs-select-color);
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
display: block;
|
|
164
|
+
font-family: var(
|
|
165
|
+
--rs-font-family-sans,
|
|
166
|
+
'Inter',
|
|
167
|
+
'Segoe UI',
|
|
168
|
+
Roboto,
|
|
169
|
+
Helvetica,
|
|
170
|
+
Arial,
|
|
171
|
+
sans-serif
|
|
172
|
+
);
|
|
173
|
+
font-size: var(--rs-select-font-size);
|
|
174
|
+
line-height: var(--rs-line-height-base, 1.45);
|
|
175
|
+
margin: 0;
|
|
176
|
+
min-height: var(--rs-select-min-height);
|
|
177
|
+
padding-block: var(--rs-select-padding-block, var(--rs-space-2, 8px));
|
|
178
|
+
padding-inline: var(--rs-select-padding-inline);
|
|
179
|
+
padding-inline-end: calc(var(--rs-select-padding-inline) + var(--rs-space-6, 24px));
|
|
180
|
+
transition:
|
|
181
|
+
background-color var(--rs-transition-fast, 140ms) ease,
|
|
182
|
+
border-color var(--rs-transition-fast, 140ms) ease;
|
|
183
|
+
width: 100%;
|
|
184
|
+
|
|
185
|
+
&:hover {
|
|
186
|
+
--rs-select-border-color: var(--rs-color-accent, #2f5bdb);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
&:focus-visible {
|
|
190
|
+
outline: 2px solid var(--rs-select-focus-ring-color);
|
|
191
|
+
outline-offset: 2px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
&:disabled {
|
|
195
|
+
cursor: not-allowed;
|
|
196
|
+
opacity: 0.55;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (prefers-reduced-motion: reduce) {
|
|
200
|
+
transition: none;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// The error state owns a coordinated colour set, so the modifier reassigns the locals the
|
|
205
|
+
// control already reads instead of restating background, border and colour. It follows the
|
|
206
|
+
// control's `:hover` so its danger border and focus ring outrank the hover accent.
|
|
207
|
+
&--invalid &__control {
|
|
208
|
+
--rs-select-bg: var(--rs-select-invalid-bg);
|
|
209
|
+
--rs-select-border-color: var(--rs-select-invalid-border-color);
|
|
210
|
+
--rs-select-focus-ring-color: var(--rs-select-invalid-focus-ring-color);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
&__icon {
|
|
214
|
+
color: var(--rs-select-icon-color);
|
|
215
|
+
inset-block-start: 50%;
|
|
216
|
+
inset-inline-end: var(--rs-select-padding-inline);
|
|
217
|
+
pointer-events: none;
|
|
218
|
+
position: absolute;
|
|
219
|
+
transform: translateY(-50%);
|
|
220
|
+
transition:
|
|
221
|
+
color var(--rs-transition-fast, 140ms) ease,
|
|
222
|
+
opacity var(--rs-transition-fast, 140ms) ease;
|
|
223
|
+
|
|
224
|
+
@media (prefers-reduced-motion: reduce) {
|
|
225
|
+
transition: none;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// The chevron dims with the control, so the pair reads as one disabled unit.
|
|
230
|
+
&__control:disabled + .rs-select__icon {
|
|
231
|
+
opacity: 0.55;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
</style>
|