@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.
@@ -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,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,193 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ export type TableTag = 'div' | 'section'
6
+
7
+ export type TableProps = {
8
+ as?: TableTag
9
+ striped?: boolean
10
+ compact?: boolean
11
+ hover?: boolean
12
+ caption?: string
13
+ label?: string
14
+ class?: string
15
+ children?: Snippet
16
+ } & HTMLAttributes<HTMLElement>
17
+
18
+ let {
19
+ as = 'div',
20
+ striped = false,
21
+ compact = false,
22
+ hover = false,
23
+ caption,
24
+ label,
25
+ class: className = '',
26
+ children,
27
+ ...attrs
28
+ }: TableProps = $props()
29
+
30
+ let holder = $state<HTMLElement | null>(null)
31
+ let table = $state<HTMLTableElement | null>(null)
32
+ let overflows = $state(false)
33
+
34
+ const hasCaption = $derived(Boolean(caption?.trim()))
35
+
36
+ /**
37
+ * Names the scroll region. The caption names the table, so it can name the region too; when
38
+ * there is no caption, `label` is the only hook for an accessible name.
39
+ */
40
+ const regionLabel = $derived(label?.trim() || caption?.trim() || 'Table')
41
+
42
+ const holderClasses = $derived(['rs-table-holder', className].filter(Boolean).join(' '))
43
+
44
+ /**
45
+ * The scroll region is announced only once the holder actually scrolls, and only for the keys
46
+ * the consumer left unset, so an explicit `role`, `tabindex`, or `aria-label` always wins.
47
+ */
48
+ const regionAttrs = $derived.by(() => {
49
+ if (!overflows) return {}
50
+ const region: Record<string, unknown> = {}
51
+ if (!('role' in attrs)) region.role = 'region'
52
+ if (!('tabindex' in attrs)) region.tabindex = 0
53
+ if (!('aria-label' in attrs)) region['aria-label'] = regionLabel
54
+ return region
55
+ })
56
+
57
+ const tableClasses = $derived(
58
+ [
59
+ 'rs-table',
60
+ striped ? 'rs-table--striped' : undefined,
61
+ compact ? 'rs-table--compact' : undefined,
62
+ hover ? 'rs-table--hover' : undefined,
63
+ ]
64
+ .filter(Boolean)
65
+ .join(' '),
66
+ )
67
+
68
+ /**
69
+ * A scroll container only becomes reachable by keyboard once it actually overflows, so the
70
+ * region semantics come from a measurement rather than being applied unconditionally: a
71
+ * table that fits stays out of the tab order and off the landmark list. Both the holder and
72
+ * the table are observed so a holder resize and a content resize each re-measure.
73
+ */
74
+ $effect(() => {
75
+ const holderElement = holder
76
+ const tableElement = table
77
+ if (!holderElement || !tableElement) return
78
+
79
+ const measure = () => {
80
+ overflows = holderElement.scrollWidth > holderElement.clientWidth
81
+ }
82
+
83
+ measure()
84
+
85
+ if (typeof ResizeObserver === 'undefined') return
86
+
87
+ const observer = new ResizeObserver(measure)
88
+ observer.observe(holderElement)
89
+ observer.observe(tableElement)
90
+
91
+ return () => observer.disconnect()
92
+ })
93
+ </script>
94
+
95
+ <svelte:element this={as} {...attrs} {...regionAttrs} bind:this={holder} class={holderClasses}>
96
+ <table class={tableClasses} bind:this={table}>
97
+ {#if hasCaption}
98
+ <caption>{caption}</caption>
99
+ {/if}
100
+ {@render children?.()}
101
+ </table>
102
+ </svelte:element>
103
+
104
+ <style lang="scss">
105
+ @use '@labcat2020/rocketship/styles/mixins' as *;
106
+
107
+ :global {
108
+ .rs-table-holder {
109
+ overflow-x: auto;
110
+ -webkit-overflow-scrolling: touch;
111
+ background: var(--rs-color-surface, #ffffff);
112
+ border: var(--rs-table-border-width, var(--rs-border-width-sm, 1px)) solid
113
+ var(--rs-table-border-color, var(--rs-color-border, #c8d2eb));
114
+ border-radius: var(--rs-table-border-radius, var(--rs-radius-md, 10px));
115
+
116
+ &:focus-visible {
117
+ outline: 2px solid var(--rs-table-focus-ring-color, var(--rs-color-focus-ring, #296dff));
118
+ outline-offset: 2px;
119
+ }
120
+ }
121
+
122
+ .rs-table {
123
+ border-collapse: collapse;
124
+ border-spacing: 0;
125
+ width: 100%;
126
+ font-family: var(
127
+ --rs-font-family-sans,
128
+ 'Inter',
129
+ 'Segoe UI',
130
+ Roboto,
131
+ Helvetica,
132
+ Arial,
133
+ sans-serif
134
+ );
135
+ font-size: var(--rs-table-font-size, var(--rs-font-size-sm, #{to-rem(14)}));
136
+ line-height: var(--rs-line-height-base, 1.45);
137
+ color: var(--rs-color-fg, #131a2f);
138
+ text-align: left;
139
+
140
+ th,
141
+ td {
142
+ padding-block: var(--rs-table-cell-padding-block, var(--rs-space-2, 8px));
143
+ padding-inline: var(--rs-table-cell-padding-inline, var(--rs-space-3, 12px));
144
+ text-align: left;
145
+ vertical-align: top;
146
+ border-bottom: var(--rs-table-border-width, var(--rs-border-width-sm, 1px)) solid
147
+ var(--rs-table-border-color, var(--rs-color-border, #c8d2eb));
148
+ }
149
+
150
+ thead th {
151
+ background: var(--rs-table-head-bg, var(--rs-color-surface-elevated, #eef2ff));
152
+ color: var(--rs-table-head-color, var(--rs-color-fg, #131a2f));
153
+ font-weight: var(--rs-font-weight-semibold, 600);
154
+ white-space: nowrap;
155
+ border-bottom-width: var(--rs-table-head-border-width, var(--rs-border-width-sm, 1px));
156
+ }
157
+
158
+ tbody tr:last-child td {
159
+ border-bottom: 0;
160
+ }
161
+
162
+ caption {
163
+ caption-side: top;
164
+ color: var(--rs-color-muted, #4f5b7d);
165
+ font-size: var(--rs-font-size-sm, #{to-rem(14)});
166
+ line-height: var(--rs-line-height-base, 1.45);
167
+ padding-block: var(--rs-space-2, 8px);
168
+ padding-inline: var(--rs-space-3, 12px);
169
+ text-align: left;
170
+ }
171
+
172
+ &--striped {
173
+ tbody tr:nth-child(even) {
174
+ background: var(--rs-table-striped-bg, var(--rs-color-surface-elevated, #eef2ff));
175
+ }
176
+ }
177
+
178
+ &--hover {
179
+ tbody tr:hover {
180
+ background: var(--rs-table-hover-bg, var(--rs-color-surface-elevated, #eef2ff));
181
+ }
182
+ }
183
+
184
+ &--compact {
185
+ th,
186
+ td {
187
+ padding-block: var(--rs-table-compact-padding-block, var(--rs-space-1, 4px));
188
+ padding-inline: var(--rs-table-compact-padding-inline, var(--rs-space-2, 8px));
189
+ }
190
+ }
191
+ }
192
+ }
193
+ </style>
@@ -0,0 +1,205 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+ import type { HTMLAttributes } from 'svelte/elements'
4
+
5
+ export type TypographyTag = 'div' | 'section' | 'article'
6
+
7
+ type Props = {
8
+ as?: TypographyTag
9
+ html?: string
10
+ class?: string
11
+ children?: Snippet
12
+ } & HTMLAttributes<HTMLElement>
13
+
14
+ let { as = 'div', html, class: className = '', children, ...attrs }: Props = $props()
15
+
16
+ const classes = $derived(['rs-typography', className].filter(Boolean).join(' '))
17
+ </script>
18
+
19
+ <svelte:element this={as} {...attrs} class={classes}>
20
+ {#if html}
21
+ {@html html}
22
+ {:else}
23
+ {@render children?.()}
24
+ {/if}
25
+ </svelte:element>
26
+
27
+ <style lang="scss">
28
+ @use '@labcat2020/rocketship/styles/mixins' as *;
29
+
30
+ :global {
31
+ .rs-typography {
32
+ h1 {
33
+ @include typography-h1;
34
+ }
35
+
36
+ h2 {
37
+ @include typography-h2;
38
+ }
39
+
40
+ h3 {
41
+ @include typography-h3;
42
+ }
43
+
44
+ h4 {
45
+ @include typography-h4;
46
+ }
47
+
48
+ h5 {
49
+ @include typography-h5;
50
+ }
51
+
52
+ h6 {
53
+ @include typography-h6;
54
+ }
55
+
56
+ p {
57
+ @include typography-p;
58
+ }
59
+
60
+ a {
61
+ @include typography-a;
62
+ }
63
+
64
+ .intro-text {
65
+ @include typography-intro;
66
+ }
67
+
68
+ i,
69
+ em {
70
+ @include typography-em;
71
+ }
72
+
73
+ b,
74
+ strong {
75
+ @include typography-strong;
76
+ }
77
+
78
+ hr {
79
+ @include typography-hr;
80
+ }
81
+
82
+ li {
83
+ @include typography-li;
84
+ }
85
+
86
+ ul {
87
+ @include typography-ul;
88
+ }
89
+
90
+ ol {
91
+ @include typography-ol;
92
+ }
93
+
94
+ @include typography-list-bridges;
95
+
96
+ pre {
97
+ @include typography-pre;
98
+ }
99
+
100
+ pre code {
101
+ @include typography-pre-code;
102
+ }
103
+
104
+ code {
105
+ @include typography-code-inline;
106
+ }
107
+
108
+ kbd {
109
+ @include typography-kbd;
110
+ }
111
+
112
+ samp {
113
+ @include typography-samp;
114
+ }
115
+
116
+ blockquote {
117
+ @include typography-blockquote;
118
+ }
119
+
120
+ blockquote p {
121
+ @include typography-blockquote-p;
122
+ }
123
+
124
+ q {
125
+ @include typography-q;
126
+ }
127
+
128
+ table {
129
+ @include typography-table;
130
+ }
131
+
132
+ caption {
133
+ @include typography-caption;
134
+ }
135
+
136
+ dl {
137
+ @include typography-dl;
138
+ }
139
+
140
+ dt {
141
+ @include typography-dt;
142
+ }
143
+
144
+ dd {
145
+ @include typography-dd;
146
+ }
147
+
148
+ small {
149
+ @include typography-small;
150
+ }
151
+
152
+ sub {
153
+ @include typography-sub;
154
+ }
155
+
156
+ sup {
157
+ @include typography-sup;
158
+ }
159
+
160
+ mark {
161
+ @include typography-mark;
162
+ }
163
+
164
+ abbr[title] {
165
+ @include typography-abbr;
166
+ }
167
+
168
+ del {
169
+ @include typography-del;
170
+ }
171
+
172
+ ins {
173
+ @include typography-ins;
174
+ }
175
+
176
+ address {
177
+ @include typography-address;
178
+ }
179
+
180
+ dfn {
181
+ @include typography-dfn;
182
+ }
183
+
184
+ cite {
185
+ @include typography-cite;
186
+ }
187
+
188
+ var {
189
+ @include typography-var;
190
+ }
191
+
192
+ time {
193
+ @include typography-time;
194
+ }
195
+
196
+ > :first-child {
197
+ margin-top: 0;
198
+ }
199
+
200
+ > :last-child {
201
+ margin-bottom: 0;
202
+ }
203
+ }
204
+ }
205
+ </style>