@labcat2020/rocketship-svelte 0.0.0-stage → 0.2.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 CHANGED
@@ -1,3 +1,99 @@
1
- # Temporary Holding Version
1
+ # @labcat2020/rocketship-svelte
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Svelte 5 port of the Rocketship component library. Components reuse the same design system as the Astro package (`@labcat2020/rocketship`): design tokens, the mixin barrel, and the base resets are shared, not duplicated.
4
+
5
+ Components are either copy-style ports of their Astro originals, or Svelte-first components with no Astro original yet. Either way they render the same BEM classes and follow the same props, token, `:global` style, container-query and a11y conventions, so an Astro version can be ported back from a Svelte-first component. A Svelte-first component is provisional — it may change once the matching Astro component's platinum standard lands, and Astro is the source of truth when the two diverge.
6
+
7
+ ## Components
8
+
9
+ | Component | Export | Astro original |
10
+ | ------------ | ----------------------------------------------------- | ------------------ |
11
+ | `Container` | `@labcat2020/rocketship-svelte/components/Container` | `Container.astro` |
12
+ | `Grid` | `@labcat2020/rocketship-svelte/components/Grid` | `Grid.astro` |
13
+ | `Card` | `@labcat2020/rocketship-svelte/components/Card` | `Card.astro` |
14
+ | `Typography` | `@labcat2020/rocketship-svelte/components/Typography` | `Typography.astro` |
15
+ | `Button` | `@labcat2020/rocketship-svelte/components/Button` | `Button.astro` |
16
+ | `Alert` | `@labcat2020/rocketship-svelte/components/Alert` | `Alert.astro` |
17
+ | `Table` | `@labcat2020/rocketship-svelte/components/Table` | — (Svelte-first) |
18
+ | `Field` | `@labcat2020/rocketship-svelte/components/Field` | — (Svelte-first) |
19
+ | `Label` | `@labcat2020/rocketship-svelte/components/Label` | — (Svelte-first) |
20
+ | `Disclosure` | `@labcat2020/rocketship-svelte/components/Disclosure` | — (Svelte-first) |
21
+ | `Accordion` | `@labcat2020/rocketship-svelte/components/Accordion` | — (Svelte-first) |
22
+ | `Figure` | `@labcat2020/rocketship-svelte/components/Figure` | — (Svelte-first) |
23
+
24
+ Import components from their `./components/*` subpaths. Those subpaths are the package's public surface; deep imports such as `@labcat2020/rocketship-svelte/src/components/Container.svelte` are not exported and will not resolve.
25
+
26
+ ```svelte
27
+ <script lang="ts">
28
+ import Container from '@labcat2020/rocketship-svelte/components/Container'
29
+ import Grid from '@labcat2020/rocketship-svelte/components/Grid'
30
+ import Card from '@labcat2020/rocketship-svelte/components/Card'
31
+ import Typography from '@labcat2020/rocketship-svelte/components/Typography'
32
+ import Button from '@labcat2020/rocketship-svelte/components/Button'
33
+ </script>
34
+
35
+ <Container as="main" size="content" padded>
36
+ <Typography as="article">
37
+ <h1>Title</h1>
38
+ <p>Body copy.</p>
39
+ </Typography>
40
+ <Grid columns={3}>
41
+ <Card>…</Card>
42
+ <Card>…</Card>
43
+ <Card>…</Card>
44
+ </Grid>
45
+ <Button variant="primary">Continue</Button>
46
+ </Container>
47
+ ```
48
+
49
+ Each component spreads unknown attributes onto its rendered element and composes an incoming `class` with its BEM block.
50
+
51
+ | Component | Props |
52
+ | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | `Container` | `as` (`div`, `section`, `main`, `article`, `header`, `footer`, `nav`), `size` (`default`, `content`, `wide`, `full`), `padded`, `class` |
54
+ | `Grid` | `as` (`div`, `ul`, `ol`), `columns` (`2`, `3`, `4`), `class` |
55
+ | `Card` | `as` (`article`, `section`, `div`, `li`), `media` snippet, `meta` snippet, `class` |
56
+ | `Typography` | `as` (`div`, `section`, `article`), `html`, `class` |
57
+ | `Button` | `as` (`button`, `a`), `href`, `variant` (`primary`, `secondary`, `ghost`), `size` (`small`, `medium`, `large`), `disabled`, `type`, `class` |
58
+ | `Alert` | `as` (`div`, `section`, `aside`, `article`), `variant` (`info`, `success`, `warning`, `error`), `title`, `titleSlot` snippet, `role`, `class` |
59
+ | `Figure` | `as` (`figure`, `div`), `variant` (`default`, `bordered`), `captionAlign` (`start`, `center`), `caption`, `captionSlot` snippet, `class` |
60
+
61
+ ### `Typography` `html` prop
62
+
63
+ `Typography` keeps the `html` prop: pass a string and it renders as raw markup inside the wrapper, taking precedence over the `children` snippet. It is the Svelte equivalent of the Astro component's `set:html`.
64
+
65
+ **Sanitize before you pass it.** The prop renders trusted HTML verbatim and does not sanitize its input, so it is only safe for content you control or content already sanitized at the boundary. Never pass unsanitized user input, request data, or third-party HTML through it — run it through a sanitizer (for example DOMPurify) first, or render structured markup with the `children` snippet instead.
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ npm install @labcat2020/rocketship-svelte @labcat2020/rocketship
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ Import the base styles (tokens + default theme) once in your app, exactly as with the Astro package:
76
+
77
+ ```js
78
+ import '@labcat2020/rocketship/styles'
79
+ ```
80
+
81
+ Inside a component's `<style lang="scss">` block, import the shared mixin barrel:
82
+
83
+ ```scss
84
+ @use '@labcat2020/rocketship/styles/mixins' as *;
85
+ ```
86
+
87
+ `./styles/mixins` is a stable export of `@labcat2020/rocketship` and resolves to `src/styles/mixins.scss`, the barrel that forwards the functions, breakpoint, container-query, and typography mixins. Import the barrel — never individual partials under `mixins/`.
88
+
89
+ ## Theming
90
+
91
+ Theming is CSS-variable based and unchanged from the Astro library. Components reference the global `--rs-*` tokens and expose `--rs-<component>-*` override hooks. Adopters can override tokens and component variables from `:root` or any ancestor.
92
+
93
+ ## Scripts
94
+
95
+ ```bash
96
+ pnpm build # compile TypeScript
97
+ pnpm check # svelte-check
98
+ pnpm test # node --test
99
+ ```
@@ -0,0 +1,13 @@
1
+ export { default as Accordion } from './components/Accordion.svelte';
2
+ export { default as Disclosure } from './components/Disclosure.svelte';
3
+ export { default as Field } from './components/Field.svelte';
4
+ export { default as Figure } from './components/Figure.svelte';
5
+ export { default as Label } from './components/Label.svelte';
6
+ export { default as Table } from './components/Table.svelte';
7
+ export type * from './components/Accordion.svelte';
8
+ export type * from './components/Disclosure.svelte';
9
+ export type * from './components/Field.svelte';
10
+ export type * from './components/Figure.svelte';
11
+ export type * from './components/Label.svelte';
12
+ export type * from './components/Table.svelte';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,+BAA+B,CAAA;AACpE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,gCAAgC,CAAA;AACtE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,2BAA2B,CAAA;AAC5D,mBAAmB,+BAA+B,CAAA;AAClD,mBAAmB,gCAAgC,CAAA;AACnD,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,4BAA4B,CAAA;AAC/C,mBAAmB,2BAA2B,CAAA;AAC9C,mBAAmB,2BAA2B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference types="svelte" />
2
+ export { default as Accordion } from './components/Accordion.svelte';
3
+ export { default as Disclosure } from './components/Disclosure.svelte';
4
+ export { default as Field } from './components/Field.svelte';
5
+ export { default as Figure } from './components/Figure.svelte';
6
+ export { default as Label } from './components/Label.svelte';
7
+ export { default as Table } from './components/Table.svelte';
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@labcat2020/rocketship-svelte",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Rocketship Svelte port — Svelte 5 components that reuse the Rocketship design system (tokens, mixins, base resets)",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/LABCAT/rocketship.git",
11
+ "directory": "packages/svelte"
12
+ },
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.js",
20
+ "default": "./dist/index.js"
21
+ },
22
+ "./components/Alert": "./src/components/Alert.svelte",
23
+ "./components/Accordion": "./src/components/Accordion.svelte",
24
+ "./components/Button": "./src/components/Button.svelte",
25
+ "./components/Card": "./src/components/Card.svelte",
26
+ "./components/Container": "./src/components/Container.svelte",
27
+ "./components/Disclosure": "./src/components/Disclosure.svelte",
28
+ "./components/Field": "./src/components/Field.svelte",
29
+ "./components/Figure": "./src/components/Figure.svelte",
30
+ "./components/Grid": "./src/components/Grid.svelte",
31
+ "./components/Label": "./src/components/Label.svelte",
32
+ "./components/Table": "./src/components/Table.svelte",
33
+ "./components/Typography": "./src/components/Typography.svelte"
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "src/components",
38
+ "README.md"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "@labcat2020/rocketship": "0.2.0"
45
+ },
46
+ "devDependencies": {
47
+ "@sveltejs/vite-plugin-svelte": "^7.3.1",
48
+ "axe-core": "4.11.1",
49
+ "jsdom": "^30.1.2",
50
+ "sass": "^1.105.1",
51
+ "svelte": "^5.57.2",
52
+ "svelte-check": "^4.7.6",
53
+ "vite": "^8.2.1"
54
+ },
55
+ "scripts": {
56
+ "build": "tsc -p tsconfig.json",
57
+ "check": "svelte-check --tsconfig ./tsconfig.json",
58
+ "test": "node --test"
59
+ }
6
60
  }
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte'
3
+ import type { Snippet } from 'svelte'
4
+ import type { HTMLAttributes } from 'svelte/elements'
5
+ import { setAccordion, type AccordionItem } from './Disclosure.svelte'
6
+
7
+ export type AccordionMode = 'multiple' | 'single'
8
+ export type AccordionTag = 'div' | 'section' | 'article' | 'ul'
9
+
10
+ type Props = {
11
+ as?: AccordionTag
12
+ mode?: AccordionMode
13
+ class?: string
14
+ children?: Snippet
15
+ } & HTMLAttributes<HTMLElement>
16
+
17
+ let {
18
+ as = 'div',
19
+ mode = 'multiple',
20
+ class: className = '',
21
+ children,
22
+ ...attrs
23
+ }: Props = $props()
24
+
25
+ const items = new Set<AccordionItem>()
26
+ let lastOpened: AccordionItem | undefined
27
+
28
+ const closeSiblings = (item: AccordionItem) => {
29
+ if (mode !== 'single') return
30
+ for (const other of items) {
31
+ if (other !== item) other.close()
32
+ }
33
+ }
34
+
35
+ setAccordion({
36
+ register: (item) => {
37
+ items.add(item)
38
+ },
39
+ unregister: (item) => {
40
+ items.delete(item)
41
+ if (lastOpened === item) lastOpened = undefined
42
+ },
43
+ opened: (item) => {
44
+ lastOpened = item
45
+ closeSiblings(item)
46
+ },
47
+ })
48
+
49
+ // Switching to single mode at runtime must reconcile panels that are already open,
50
+ // keeping the most recently opened one. `untrack` avoids subscribing to child state.
51
+ $effect(() => {
52
+ if (mode !== 'single') return
53
+ untrack(() => {
54
+ const open = [...items].filter((item) => item.isOpen())
55
+ if (open.length <= 1) return
56
+ const keep = lastOpened && open.includes(lastOpened) ? lastOpened : open[open.length - 1]
57
+ for (const item of open) {
58
+ if (item !== keep) item.close()
59
+ }
60
+ })
61
+ })
62
+
63
+ const classes = $derived(
64
+ ['rs-accordion', mode === 'single' ? 'rs-accordion--single' : undefined, className]
65
+ .filter(Boolean)
66
+ .join(' '),
67
+ )
68
+ </script>
69
+
70
+ <svelte:element this={as} {...attrs} class={classes}>
71
+ {@render children?.()}
72
+ </svelte:element>
73
+
74
+ <style lang="scss">
75
+ @use '@labcat2020/rocketship/styles/mixins' as *;
76
+
77
+ :global {
78
+ .rs-accordion {
79
+ --rs-accordion-gap: var(--rs-space-2, 8px);
80
+ display: flex;
81
+ flex-direction: column;
82
+ gap: var(--rs-accordion-gap);
83
+
84
+ &--single {
85
+ --rs-accordion-gap: 0;
86
+ }
87
+ }
88
+ }
89
+ </style>
@@ -0,0 +1,129 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ export type AlertVariant = 'info' | 'success' | 'warning' | 'error'
6
+ export type AlertTag = 'div' | 'section' | 'aside' | 'article'
7
+
8
+ type Props = {
9
+ as?: AlertTag
10
+ variant?: AlertVariant
11
+ title?: string
12
+ role?: 'alert' | 'status'
13
+ class?: string
14
+ titleSlot?: Snippet
15
+ children?: Snippet
16
+ } & HTMLAttributes<HTMLElement>
17
+
18
+ let {
19
+ as = 'div',
20
+ variant = 'info',
21
+ title,
22
+ role: roleProp,
23
+ class: className = '',
24
+ titleSlot,
25
+ children,
26
+ ...attrs
27
+ }: Props = $props()
28
+
29
+ const role = $derived(roleProp ?? (variant === 'error' || variant === 'warning' ? 'alert' : 'status'))
30
+
31
+ const classes = $derived(['rs-alert', `rs-alert--${variant}`, className].filter(Boolean).join(' '))
32
+ </script>
33
+
34
+ <svelte:element this={as} {...attrs} class={classes} role={role}>
35
+ {#if title}
36
+ <h4 class="rs-alert__title">{title}</h4>
37
+ {:else if titleSlot}
38
+ <h4 class="rs-alert__title">
39
+ {@render titleSlot()}
40
+ </h4>
41
+ {/if}
42
+ <div class="rs-alert__body">
43
+ {@render children?.()}
44
+ </div>
45
+ </svelte:element>
46
+
47
+ <style lang="scss">
48
+ @use '@labcat2020/rocketship/styles/mixins' as *;
49
+
50
+ :global {
51
+ .rs-alert {
52
+ --rs-alert-bg: var(--rs-color-surface-elevated, #eef2ff);
53
+ --rs-alert-color: var(--rs-color-fg, #131a2f);
54
+ --rs-alert-border-color: var(--rs-color-border, #c8d2eb);
55
+ --rs-alert-accent-color: var(--rs-color-accent, #2f5bdb);
56
+ display: flex;
57
+ flex-direction: column;
58
+ gap: var(--rs-alert-gap, var(--rs-space-2, 8px));
59
+ font-family: var(
60
+ --rs-font-family-sans,
61
+ 'Inter',
62
+ 'Segoe UI',
63
+ Roboto,
64
+ Helvetica,
65
+ Arial,
66
+ sans-serif
67
+ );
68
+ background: var(--rs-alert-bg);
69
+ border: var(--rs-alert-border-width, var(--rs-border-width-sm, 1px)) solid
70
+ var(--rs-alert-border-color);
71
+ border-inline-start: var(--rs-alert-accent-width, var(--rs-border-width-lg, 4px)) solid
72
+ var(--rs-alert-accent-color);
73
+ border-radius: var(--rs-alert-border-radius, var(--rs-radius-md, 10px));
74
+ color: var(--rs-alert-color);
75
+ padding-block: var(--rs-alert-padding-block, var(--rs-space-3, 12px));
76
+ padding-inline: var(--rs-alert-padding-inline, var(--rs-space-4, 16px));
77
+
78
+ @include container-query-up($rs-cq-sm) {
79
+ gap: var(--rs-alert-gap, var(--rs-space-3, 12px));
80
+ padding-block: var(--rs-alert-padding-block, var(--rs-space-4, 16px));
81
+ padding-inline: var(--rs-alert-padding-inline, var(--rs-space-5, 20px));
82
+ }
83
+
84
+ &--success {
85
+ --rs-alert-bg: var(--rs-color-success-bg, #e6f4ea);
86
+ --rs-alert-border-color: var(--rs-color-success-border, #a7d8b5);
87
+ --rs-alert-accent-color: var(--rs-color-success-accent, #1a7a32);
88
+ --rs-alert-color: var(--rs-color-success-fg, #0f2a18);
89
+ }
90
+
91
+ &--warning {
92
+ --rs-alert-bg: var(--rs-color-warning-bg, #fff8e1);
93
+ --rs-alert-border-color: var(--rs-color-warning-border, #f0c36a);
94
+ --rs-alert-accent-color: var(--rs-color-warning-accent, #9a5d00);
95
+ --rs-alert-color: var(--rs-color-warning-fg, #3d2800);
96
+ }
97
+
98
+ &--error {
99
+ --rs-alert-bg: var(--rs-color-danger-bg, #ffeef0);
100
+ --rs-alert-border-color: var(--rs-color-danger-border, #f0a8b0);
101
+ --rs-alert-accent-color: var(--rs-color-danger-accent, #c5221f);
102
+ --rs-alert-color: var(--rs-color-danger-fg, #3d0a08);
103
+ }
104
+
105
+ &__title {
106
+ color: var(--rs-alert-color);
107
+ font-size: var(--rs-alert-title-font-size, var(--rs-font-size-md, #{to-rem(16)}));
108
+ font-weight: var(--rs-alert-title-font-weight, var(--rs-font-weight-semibold, 600));
109
+ line-height: var(--rs-alert-title-line-height, var(--rs-line-height-tight, 1.2));
110
+ margin: 0;
111
+
112
+ @include container-query-up($rs-cq-sm) {
113
+ font-size: var(--rs-alert-title-font-size, var(--rs-font-size-lg, #{to-rem(18)}));
114
+ }
115
+ }
116
+
117
+ &__body {
118
+ color: var(--rs-alert-color);
119
+ font-size: var(--rs-alert-body-font-size, var(--rs-font-size-sm, #{to-rem(14)}));
120
+ line-height: var(--rs-alert-body-line-height, var(--rs-line-height-base, 1.45));
121
+ margin: 0;
122
+
123
+ @include container-query-up($rs-cq-sm) {
124
+ font-size: var(--rs-alert-body-font-size, var(--rs-font-size-md, #{to-rem(16)}));
125
+ }
126
+ }
127
+ }
128
+ }
129
+ </style>
@@ -0,0 +1,187 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements'
4
+
5
+ export type ButtonVariant = 'primary' | 'secondary' | 'ghost'
6
+ export type ButtonSize = 'small' | 'medium' | 'large'
7
+ export type ButtonTag = 'button' | 'a'
8
+
9
+ type NativeButtonType = 'button' | 'submit' | 'reset'
10
+
11
+ type Props = {
12
+ as?: ButtonTag
13
+ href?: string
14
+ variant?: ButtonVariant
15
+ size?: ButtonSize
16
+ disabled?: boolean
17
+ type?: NativeButtonType
18
+ class?: string
19
+ children?: Snippet
20
+ } & HTMLButtonAttributes &
21
+ HTMLAnchorAttributes
22
+
23
+ let {
24
+ as = 'button',
25
+ href,
26
+ variant = 'primary',
27
+ size = 'medium',
28
+ disabled = false,
29
+ type = 'button',
30
+ class: className = '',
31
+ children,
32
+ ...attrs
33
+ }: Props = $props()
34
+
35
+ const classes = $derived(
36
+ ['rs-button', `rs-button--${variant}`, `rs-button--${size}`, className].filter(Boolean).join(' '),
37
+ )
38
+
39
+ const shouldRenderAsLink = $derived(as === 'a' || Boolean(href))
40
+ </script>
41
+
42
+ {#if shouldRenderAsLink}
43
+ <a
44
+ {...attrs}
45
+ aria-disabled={disabled || undefined}
46
+ class={classes}
47
+ href={disabled ? undefined : (href ?? '#')}
48
+ role={disabled ? 'link' : undefined}
49
+ tabindex={disabled ? -1 : undefined}
50
+ >
51
+ {@render children?.()}
52
+ </a>
53
+ {:else}
54
+ <button {...attrs} class={classes} disabled={disabled} type={type}>
55
+ {@render children?.()}
56
+ </button>
57
+ {/if}
58
+
59
+ <style lang="scss">
60
+ @use '@labcat2020/rocketship/styles/mixins' as *;
61
+
62
+ :global {
63
+ .rs-button {
64
+ all: unset;
65
+ --rs-button-bg: var(--rs-color-accent, #2f5bdb);
66
+ --rs-button-color: var(--rs-color-accent-contrast, #ffffff);
67
+ --rs-button-border-color: var(--rs-color-accent, #2f5bdb);
68
+ --rs-button-border-width: var(--rs-border-width, 1px);
69
+ --rs-button-border-radius: var(--rs-radius-pill, 999px);
70
+ --rs-button-font-size: var(--rs-font-size-sm, #{to-rem(14)});
71
+ --rs-button-font-weight: var(--rs-font-weight-semibold, 600);
72
+ --rs-button-line-height: var(--rs-line-height-tight, 1.2);
73
+ --rs-button-padding-inline: var(--rs-space-3, 12px);
74
+ --rs-button-min-height: 36px;
75
+ --rs-button-gap: var(--rs-space-2, 8px);
76
+ --rs-button-focus-ring-color: var(--rs-color-focus-ring, #296dff);
77
+ box-sizing: border-box;
78
+ align-items: center;
79
+ background: var(--rs-button-bg);
80
+ border: var(--rs-button-border-width) solid var(--rs-button-border-color);
81
+ border-radius: var(--rs-button-border-radius);
82
+ color: var(--rs-button-color);
83
+ cursor: pointer;
84
+ display: inline-flex;
85
+ font-family: var(
86
+ --rs-font-family-sans,
87
+ 'Inter',
88
+ 'Segoe UI',
89
+ Roboto,
90
+ Helvetica,
91
+ Arial,
92
+ sans-serif
93
+ );
94
+ font-size: var(--rs-button-font-size);
95
+ font-weight: var(--rs-button-font-weight);
96
+ gap: var(--rs-button-gap);
97
+ justify-content: center;
98
+ line-height: var(--rs-button-line-height);
99
+ min-height: var(--rs-button-min-height);
100
+ padding-block: 8px;
101
+ padding-inline: var(--rs-button-padding-inline);
102
+ text-decoration: none;
103
+ transition:
104
+ background-color var(--rs-transition-fast, 140ms) ease,
105
+ border-color var(--rs-transition-fast, 140ms) ease,
106
+ color var(--rs-transition-fast, 140ms) ease,
107
+ opacity var(--rs-transition-fast, 140ms) ease;
108
+ white-space: nowrap;
109
+
110
+ &:hover {
111
+ filter: brightness(1.08);
112
+ }
113
+
114
+ &:active {
115
+ filter: brightness(0.94);
116
+ }
117
+
118
+ &:focus-visible {
119
+ outline: 2px solid var(--rs-button-focus-ring-color);
120
+ outline-offset: 2px;
121
+ }
122
+
123
+ &:disabled,
124
+ &[aria-disabled='true'] {
125
+ cursor: not-allowed;
126
+ opacity: 0.55;
127
+ pointer-events: none;
128
+ }
129
+
130
+ &--secondary {
131
+ --rs-button-bg: var(--rs-color-surface, #ffffff);
132
+ --rs-button-border-color: var(--rs-color-accent, #2f5bdb);
133
+ --rs-button-border-width: var(--rs-border-width-md, 2px);
134
+ --rs-button-color: var(--rs-color-accent, #2f5bdb);
135
+
136
+ &:hover {
137
+ filter: none;
138
+ --rs-button-bg: var(--rs-color-surface-elevated, #eef2ff);
139
+ --rs-button-border-color: var(--rs-color-link-hover, #173ea8);
140
+ --rs-button-color: var(--rs-color-link-hover, #173ea8);
141
+ }
142
+ }
143
+
144
+ &--ghost {
145
+ --rs-button-bg: transparent;
146
+ --rs-button-border-color: transparent;
147
+ --rs-button-border-width: var(--rs-border-width-md, 2px);
148
+ --rs-button-color: var(--rs-color-accent, #2f5bdb);
149
+
150
+ &:hover {
151
+ filter: none;
152
+ --rs-button-bg: var(--rs-color-surface-elevated, #eef2ff);
153
+ }
154
+ }
155
+
156
+ &--small {
157
+ --rs-button-font-size: var(--rs-button-font-size-sm);
158
+ --rs-button-min-height: var(--rs-button-min-height-sm);
159
+ --rs-button-padding-inline: var(--rs-button-padding-inline-sm);
160
+ }
161
+
162
+ &--medium {
163
+ --rs-button-font-size: var(--rs-button-font-size-md);
164
+ --rs-button-min-height: var(--rs-button-min-height-md);
165
+ --rs-button-padding-inline: var(--rs-button-padding-inline-md);
166
+ }
167
+
168
+ &--large {
169
+ --rs-button-font-size: var(--rs-button-font-size-lg);
170
+ --rs-button-min-height: var(--rs-button-min-height-lg);
171
+ --rs-button-padding-inline: var(--rs-button-padding-inline-lg);
172
+ }
173
+
174
+ @include container-query-up($rs-cq-sm) {
175
+ --rs-button-font-size-sm: var(--rs-button-font-size-sm-cq);
176
+ --rs-button-font-size-md: var(--rs-button-font-size-md-cq);
177
+ --rs-button-font-size-lg: var(--rs-button-font-size-lg-cq);
178
+ --rs-button-min-height-sm: var(--rs-button-min-height-sm-cq);
179
+ --rs-button-min-height-md: var(--rs-button-min-height-md-cq);
180
+ --rs-button-min-height-lg: var(--rs-button-min-height-lg-cq);
181
+ --rs-button-padding-inline-sm: var(--rs-button-padding-inline-sm-cq);
182
+ --rs-button-padding-inline-md: var(--rs-button-padding-inline-md-cq);
183
+ --rs-button-padding-inline-lg: var(--rs-button-padding-inline-lg-cq);
184
+ }
185
+ }
186
+ }
187
+ </style>