@purestack/ts-components 1.1.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/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/ts-components.d.mts +1662 -0
- package/dist/ts-components.d.mts.map +1 -0
- package/dist/ts-components.mjs +9200 -0
- package/dist/ts-components.mjs.map +1 -0
- package/package.json +130 -0
- package/src/defineComponents.ts +87 -0
- package/src/index.ts +270 -0
- package/src/registerStyles.ts +63 -0
- package/src/standard/alertBox/alertBox.ts +64 -0
- package/src/standard/autoId.ts +25 -0
- package/src/standard/badge/badge.ts +41 -0
- package/src/standard/badge/badgeStyle.ts +21 -0
- package/src/standard/barChart/barChart.ts +338 -0
- package/src/standard/barChart/barChartStyle.ts +81 -0
- package/src/standard/btn/btn.ts +191 -0
- package/src/standard/btn/btnStyle.ts +59 -0
- package/src/standard/btnGroup/btnGroup.ts +208 -0
- package/src/standard/btnGroup/btnGroupStyle.ts +85 -0
- package/src/standard/chart/cartesianChart.ts +99 -0
- package/src/standard/chart/chartDefaults.ts +25 -0
- package/src/standard/chart/chartUtils.ts +84 -0
- package/src/standard/classicLogo/classicLogo.ts +429 -0
- package/src/standard/classicLogo/classicLogoStyle.ts +292 -0
- package/src/standard/componentVariant.ts +153 -0
- package/src/standard/composer/composer.ts +1035 -0
- package/src/standard/composer/composerCanvasInitialStyle.ts +225 -0
- package/src/standard/composer/composerCanvasRevertStyle.ts +14 -0
- package/src/standard/composer/composerCanvasSharedStyle.ts +60 -0
- package/src/standard/composer/composerCanvasStyleTypes.ts +8 -0
- package/src/standard/composer/composerCanvasThemeStyle.ts +53 -0
- package/src/standard/composer/composerStyle.ts +156 -0
- package/src/standard/composer/emailHtmlPolicy.ts +474 -0
- package/src/standard/consent/consent.ts +173 -0
- package/src/standard/consent/consentStyle.ts +44 -0
- package/src/standard/contactForm/contactForm.ts +135 -0
- package/src/standard/contactForm/contactFormStyle.ts +116 -0
- package/src/standard/doughnutChart/doughnutChart.ts +527 -0
- package/src/standard/doughnutChart/doughnutChartStyle.ts +64 -0
- package/src/standard/dropFiles/dropFiles.ts +480 -0
- package/src/standard/dropFiles/dropFilesStyle.ts +171 -0
- package/src/standard/expandablePanel/expandablePanel.ts +106 -0
- package/src/standard/expandablePanel/expandablePanelStyle.ts +56 -0
- package/src/standard/flex/flex.ts +269 -0
- package/src/standard/flex/flexStyle.ts +556 -0
- package/src/standard/footer/footer.ts +93 -0
- package/src/standard/footer/footerStyle.ts +39 -0
- package/src/standard/form/autoCompleteInput.ts +567 -0
- package/src/standard/form/form.ts +205 -0
- package/src/standard/form/formCheck.ts +96 -0
- package/src/standard/form/formInputField.ts +184 -0
- package/src/standard/form/formSelectField.ts +152 -0
- package/src/standard/form/formStyle.ts +585 -0
- package/src/standard/form/multiAutoCompleteInput.ts +880 -0
- package/src/standard/grid/grid.ts +177 -0
- package/src/standard/grid/gridStyle.ts +56 -0
- package/src/standard/icon/icon.ts +96 -0
- package/src/standard/icon/iconStyle.ts +50 -0
- package/src/standard/landing/codeShowcase.ts +54 -0
- package/src/standard/landing/comparisonColumn.ts +30 -0
- package/src/standard/landing/comparisonFeature.ts +22 -0
- package/src/standard/landing/comparisonTable.ts +38 -0
- package/src/standard/landing/ctaSection.ts +59 -0
- package/src/standard/landing/featureCard.ts +41 -0
- package/src/standard/landing/landing.ts +52 -0
- package/src/standard/landing/landingBand.ts +217 -0
- package/src/standard/landing/landingSection.ts +43 -0
- package/src/standard/landing/landingTypes.ts +141 -0
- package/src/standard/landing/metricItem.ts +25 -0
- package/src/standard/landing/metricStrip.ts +20 -0
- package/src/standard/lineChart/lineChart.ts +462 -0
- package/src/standard/lineChart/lineChartStyle.ts +91 -0
- package/src/standard/logo/logo.ts +192 -0
- package/src/standard/logo/logoStyle.ts +164 -0
- package/src/standard/modal/modal.ts +171 -0
- package/src/standard/modal/modalStore.ts +154 -0
- package/src/standard/modal/modalStyle.ts +190 -0
- package/src/standard/navMenu/navMenu.ts +278 -0
- package/src/standard/navMenu/navMenuStyle.ts +323 -0
- package/src/standard/pageLinks/pageLinks.ts +90 -0
- package/src/standard/pageScript/pageScript.ts +155 -0
- package/src/standard/pageToc/pageToc.ts +154 -0
- package/src/standard/pageToc/pageTocStyle.ts +372 -0
- package/src/standard/panel/panel.ts +55 -0
- package/src/standard/panel/panelStyle.ts +15 -0
- package/src/standard/pricing/pricing.ts +146 -0
- package/src/standard/searchBox/searchBox.ts +54 -0
- package/src/standard/searchBox/searchBoxStyle.ts +180 -0
- package/src/standard/sectionHeader/sectionHeader.ts +99 -0
- package/src/standard/signIn/signIn.ts +167 -0
- package/src/standard/signIn/signInStyle.ts +70 -0
- package/src/standard/tabs/tabs.ts +193 -0
- package/src/standard/tabs/tabsStyle.ts +231 -0
- package/src/standard/themeToggle/themeToggle.ts +33 -0
- package/src/standard/themeToggle/themeToggleStyle.ts +88 -0
- package/src/standard/toast/toastHost.ts +42 -0
- package/src/standard/toast/toastStore.ts +50 -0
- package/src/standard/topBar/topBar.ts +101 -0
- package/src/standard/topBar/topBarStyle.ts +169 -0
- package/src/standard/virtualList/variableVirtualTable.ts +371 -0
- package/src/standard/virtualList/virtualList.ts +423 -0
- package/src/standard/virtualList/virtualListStyle.ts +34 -0
- package/src/standard/virtualList/virtualTable.ts +179 -0
- package/src/standard/virtualList/virtualTableStyle.ts +24 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
styleBuilder,
|
|
3
|
+
type ThemeMode,
|
|
4
|
+
type ThemePalette,
|
|
5
|
+
themes,
|
|
6
|
+
} from '@purestack/ts-style'
|
|
7
|
+
|
|
8
|
+
export function registerSignInStyles() {
|
|
9
|
+
themes.forEach((theme, palette) => {
|
|
10
|
+
registerSignInDisclosureStyles(theme)
|
|
11
|
+
registerSignInAuthStateStyles(theme)
|
|
12
|
+
registerSignInPanelPlacementStyles(theme)
|
|
13
|
+
registerSignInAvatarStyles(theme, palette)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function registerSignInDisclosureStyles(theme: ThemeMode) {
|
|
18
|
+
styleBuilder.select('.sign-in', theme).display('inline-block').lineHeight('0')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function registerSignInAuthStateStyles(theme: ThemeMode) {
|
|
22
|
+
styleBuilder
|
|
23
|
+
.select('.sign-in__signed-out-view, .sign-in__signed-in-view', theme)
|
|
24
|
+
.display('inline-flex')
|
|
25
|
+
.alignItems('center')
|
|
26
|
+
.justifyContent('center')
|
|
27
|
+
.width('100%')
|
|
28
|
+
.height('100%')
|
|
29
|
+
|
|
30
|
+
styleBuilder
|
|
31
|
+
.select(
|
|
32
|
+
'.sign-in__signed-in-view, :scope.signed-in .sign-in__signed-out-view',
|
|
33
|
+
theme,
|
|
34
|
+
)
|
|
35
|
+
.display('none')
|
|
36
|
+
|
|
37
|
+
styleBuilder
|
|
38
|
+
.select(':scope.signed-in .sign-in__signed-in-view', theme)
|
|
39
|
+
.display('inline-flex')
|
|
40
|
+
|
|
41
|
+
styleBuilder
|
|
42
|
+
.select(
|
|
43
|
+
'.sign-in__signed-in-action, :scope.signed-in .sign-in__signed-out-action',
|
|
44
|
+
theme,
|
|
45
|
+
)
|
|
46
|
+
.display('none')
|
|
47
|
+
|
|
48
|
+
styleBuilder
|
|
49
|
+
.select(':scope.signed-in .sign-in__signed-in-action', theme)
|
|
50
|
+
.display('inline-flex')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function registerSignInPanelPlacementStyles(theme: ThemeMode) {
|
|
54
|
+
styleBuilder
|
|
55
|
+
.select('.sign-in__panel', theme)
|
|
56
|
+
.position('absolute')
|
|
57
|
+
.display('block')
|
|
58
|
+
.top('100%')
|
|
59
|
+
.right('0')
|
|
60
|
+
.zIndex(60)
|
|
61
|
+
.minWidth('11.25rem')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function registerSignInAvatarStyles(theme: ThemeMode, palette: ThemePalette) {
|
|
65
|
+
styleBuilder
|
|
66
|
+
.select('.sign-in__avatar', theme)
|
|
67
|
+
.display('block')
|
|
68
|
+
.objectFit('cover')
|
|
69
|
+
.borderRadius(palette.radii.pill)
|
|
70
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { tryResolveTsSsgContext } from '@purestack/ts-common'
|
|
2
|
+
import type { SemanticTone } from '@purestack/ts-style'
|
|
3
|
+
import {
|
|
4
|
+
type ComponentHead,
|
|
5
|
+
type ComputedRef,
|
|
6
|
+
computed,
|
|
7
|
+
defineComponent,
|
|
8
|
+
html,
|
|
9
|
+
isRef,
|
|
10
|
+
type RefOrValue,
|
|
11
|
+
ref,
|
|
12
|
+
unref,
|
|
13
|
+
} from 'regor'
|
|
14
|
+
import { createAutoId } from '../autoId'
|
|
15
|
+
import {
|
|
16
|
+
type ComponentVariant,
|
|
17
|
+
type ComponentVariantMode,
|
|
18
|
+
resolveComponentClasses,
|
|
19
|
+
} from '../componentVariant'
|
|
20
|
+
|
|
21
|
+
const defaultGroup = 'tabs-default'
|
|
22
|
+
const DEFAULT_TABS_VARIANT_MODE: ComponentVariantMode = 'stateless'
|
|
23
|
+
const resolveTabGroup = createAutoId(defaultGroup)
|
|
24
|
+
const resolveTabId = createAutoId('tab')
|
|
25
|
+
|
|
26
|
+
export class Tabs {
|
|
27
|
+
group: string = defaultGroup
|
|
28
|
+
ariaLabel?: RefOrValue<string>
|
|
29
|
+
selectedTab?: RefOrValue<string>
|
|
30
|
+
tone?: RefOrValue<SemanticTone>
|
|
31
|
+
variant?: RefOrValue<ComponentVariant>
|
|
32
|
+
variantMode?: RefOrValue<ComponentVariantMode>
|
|
33
|
+
tabVariant?: RefOrValue<ComponentVariant>
|
|
34
|
+
classes?: ComputedRef<string>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface TabPane {
|
|
38
|
+
id?: string
|
|
39
|
+
label?: RefOrValue<string>
|
|
40
|
+
icon?: RefOrValue<string>
|
|
41
|
+
disabled?: RefOrValue<boolean>
|
|
42
|
+
group?: string
|
|
43
|
+
tone?: RefOrValue<SemanticTone>
|
|
44
|
+
variant?: RefOrValue<ComponentVariant>
|
|
45
|
+
variantMode?: RefOrValue<ComponentVariantMode>
|
|
46
|
+
tabVariant?: RefOrValue<ComponentVariant>
|
|
47
|
+
classes?: ComputedRef<string>
|
|
48
|
+
tabClasses?: ComputedRef<string>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const tabsTemplate = html`<section class="tabs" :class="classes">
|
|
52
|
+
<slot name="header"></slot>
|
|
53
|
+
<div class="tabs__list" role="tablist" :aria-label="ariaLabel || 'Tabs'">
|
|
54
|
+
<slot></slot>
|
|
55
|
+
</div>
|
|
56
|
+
</section>`
|
|
57
|
+
|
|
58
|
+
const tabPaneTemplate = html`<div class="tabs__item">
|
|
59
|
+
<input
|
|
60
|
+
class="tabs__control"
|
|
61
|
+
type="radio"
|
|
62
|
+
:name="group"
|
|
63
|
+
:id="id"
|
|
64
|
+
:value="id"
|
|
65
|
+
r-model="selectedTab"
|
|
66
|
+
:disabled="disabled"/>
|
|
67
|
+
<label
|
|
68
|
+
class="tabs__tab"
|
|
69
|
+
:class="tabClasses"
|
|
70
|
+
role="tab"
|
|
71
|
+
:id="id+'-label'"
|
|
72
|
+
:for="id"
|
|
73
|
+
:aria-controls="id+'-panel'"
|
|
74
|
+
:aria-selected="selectedTab === id"
|
|
75
|
+
:aria-disabled="disabled"
|
|
76
|
+
>
|
|
77
|
+
<Icon class="tabs__tab-icon" :name="icon"/>
|
|
78
|
+
<span class="tabs__tab-label">{{ label || id }}</span>
|
|
79
|
+
</label>
|
|
80
|
+
<section
|
|
81
|
+
class="tabs__panel"
|
|
82
|
+
:class="classes"
|
|
83
|
+
role="tabpanel"
|
|
84
|
+
:id="id+'-panel'"
|
|
85
|
+
:aria-labelledby="id+'-label'"
|
|
86
|
+
>
|
|
87
|
+
<div class="tabs__panel-body" :class="class">
|
|
88
|
+
<slot></slot>
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
91
|
+
</div>`
|
|
92
|
+
|
|
93
|
+
function defineTabsComponent() {
|
|
94
|
+
return defineComponent<Tabs>(tabsTemplate, {
|
|
95
|
+
props: [
|
|
96
|
+
'ariaLabel',
|
|
97
|
+
'group',
|
|
98
|
+
'selectedTab',
|
|
99
|
+
'tone',
|
|
100
|
+
'variant',
|
|
101
|
+
'variantMode',
|
|
102
|
+
'tabVariant',
|
|
103
|
+
],
|
|
104
|
+
context: (head) => {
|
|
105
|
+
markTabsRuntimeEmbed(head)
|
|
106
|
+
return resolveTabs(head.props)
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function defineTabPaneComponent() {
|
|
112
|
+
return defineComponent<TabPane>(tabPaneTemplate, {
|
|
113
|
+
props: [
|
|
114
|
+
'id',
|
|
115
|
+
'label',
|
|
116
|
+
'icon',
|
|
117
|
+
'disabled',
|
|
118
|
+
'group',
|
|
119
|
+
'tone',
|
|
120
|
+
'variant',
|
|
121
|
+
'variantMode',
|
|
122
|
+
'tabVariant',
|
|
123
|
+
],
|
|
124
|
+
context: (head) => resolveTabPane(head),
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function defineTabsComponents() {
|
|
129
|
+
return {
|
|
130
|
+
tabs: defineTabsComponent(),
|
|
131
|
+
tabPane: defineTabPaneComponent(),
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolveTabs(props: Tabs): Tabs {
|
|
136
|
+
const group = resolveTabGroup(unref(props.group))
|
|
137
|
+
const tabs = new Tabs()
|
|
138
|
+
const selectedTab = ensureSelectedTabIsRef(props.selectedTab)
|
|
139
|
+
return Object.assign(tabs, {
|
|
140
|
+
...props,
|
|
141
|
+
group,
|
|
142
|
+
selectedTab,
|
|
143
|
+
classes: computed(() =>
|
|
144
|
+
resolveComponentClasses(props, {
|
|
145
|
+
defaultVariant: 'surface',
|
|
146
|
+
defaultVariantMode: DEFAULT_TABS_VARIANT_MODE,
|
|
147
|
+
}),
|
|
148
|
+
),
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ensureSelectedTabIsRef(selectedTab?: RefOrValue<string>) {
|
|
153
|
+
return isRef(selectedTab) ? selectedTab : ref(selectedTab)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resolveTabPane(head: ComponentHead<TabPane>): TabPane {
|
|
157
|
+
const fromParent = head.findContext(Tabs)
|
|
158
|
+
const group = head.props.group || fromParent?.group || defaultGroup
|
|
159
|
+
const id = resolveTabId(unref(head.props.id))
|
|
160
|
+
const tone = head.props.tone || fromParent?.tone
|
|
161
|
+
const variantMode = head.props.variantMode || fromParent?.variantMode
|
|
162
|
+
const tabVariant = head.props.tabVariant || fromParent?.tabVariant
|
|
163
|
+
return {
|
|
164
|
+
...head.props,
|
|
165
|
+
id,
|
|
166
|
+
group,
|
|
167
|
+
tone,
|
|
168
|
+
variantMode,
|
|
169
|
+
tabVariant,
|
|
170
|
+
classes: computed(() =>
|
|
171
|
+
resolveComponentClasses(
|
|
172
|
+
{ tone, variant: head.props.variant, variantMode },
|
|
173
|
+
{
|
|
174
|
+
defaultVariant: 'none',
|
|
175
|
+
defaultVariantMode: DEFAULT_TABS_VARIANT_MODE,
|
|
176
|
+
},
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
tabClasses: computed(() =>
|
|
180
|
+
resolveComponentClasses(
|
|
181
|
+
{ tone, variant: tabVariant },
|
|
182
|
+
{
|
|
183
|
+
defaultVariant: 'underline',
|
|
184
|
+
classes: [unref(head.props.disabled) ? 'tabs__tab--disabled' : ''],
|
|
185
|
+
},
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function markTabsRuntimeEmbed(head: ComponentHead<Tabs>) {
|
|
192
|
+
tryResolveTsSsgContext(head)?.recordRuntimeEmbed('tabs', 'head')
|
|
193
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import {
|
|
2
|
+
styleBuilder,
|
|
3
|
+
type ThemeMode,
|
|
4
|
+
type ThemePalette,
|
|
5
|
+
themes,
|
|
6
|
+
} from '@purestack/ts-style'
|
|
7
|
+
|
|
8
|
+
export function registerTabsStyles() {
|
|
9
|
+
themes.forEach((theme, palette) => {
|
|
10
|
+
registerTabsShellStyles(theme, palette)
|
|
11
|
+
registerTabsControlStyles(theme, palette)
|
|
12
|
+
registerTabsPanelStyles(theme)
|
|
13
|
+
registerTabsResponsiveStyles(theme)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function registerTabsShellStyles(theme: ThemeMode, palette: ThemePalette) {
|
|
18
|
+
styleBuilder
|
|
19
|
+
.select('.tabs', theme)
|
|
20
|
+
.display('grid')
|
|
21
|
+
.gap('0.6em')
|
|
22
|
+
.alignContent('start')
|
|
23
|
+
.padding('1em')
|
|
24
|
+
.boxShadow(palette.effect.softShadow)
|
|
25
|
+
|
|
26
|
+
styleBuilder
|
|
27
|
+
.select('.tabs__list', theme)
|
|
28
|
+
.display('grid')
|
|
29
|
+
.gridTemplateColumns('repeat(auto-fit, minmax(7.5rem, 1fr))')
|
|
30
|
+
.gap('0.6em 0.4em')
|
|
31
|
+
.alignItems('stretch')
|
|
32
|
+
.alignContent('start')
|
|
33
|
+
.minWidth('0')
|
|
34
|
+
|
|
35
|
+
styleBuilder.select('.tabs__item', theme).display('contents')
|
|
36
|
+
|
|
37
|
+
styleBuilder
|
|
38
|
+
.select('.tabs__tabs-row', theme)
|
|
39
|
+
.display('none')
|
|
40
|
+
.alignItems('center')
|
|
41
|
+
.gap('0.5em')
|
|
42
|
+
.minWidth('0')
|
|
43
|
+
|
|
44
|
+
styleBuilder
|
|
45
|
+
.select('.tabs__tab-buttons', theme)
|
|
46
|
+
.display('flex')
|
|
47
|
+
.alignItems('center')
|
|
48
|
+
.gap('0.5em')
|
|
49
|
+
.minWidth('0')
|
|
50
|
+
.flex('1')
|
|
51
|
+
.overflow('hidden')
|
|
52
|
+
|
|
53
|
+
styleBuilder
|
|
54
|
+
.select('.tabs__overflow', theme)
|
|
55
|
+
.display('none')
|
|
56
|
+
.position('relative')
|
|
57
|
+
.flexShrink('0')
|
|
58
|
+
|
|
59
|
+
styleBuilder.select('.tabs__overflow--visible', theme).display('inline-flex')
|
|
60
|
+
|
|
61
|
+
styleBuilder
|
|
62
|
+
.select('.tabs__overflow-toggle', theme)
|
|
63
|
+
.width('2.3em')
|
|
64
|
+
.height('2.3em')
|
|
65
|
+
.padding('0')
|
|
66
|
+
|
|
67
|
+
styleBuilder
|
|
68
|
+
.select('.tabs__overflow-menu', theme)
|
|
69
|
+
.display('none')
|
|
70
|
+
.position('absolute')
|
|
71
|
+
.right('0')
|
|
72
|
+
.top('calc(100% + 0.375rem)')
|
|
73
|
+
.zIndex(20)
|
|
74
|
+
.minWidth('100%')
|
|
75
|
+
.width('max-content')
|
|
76
|
+
.maxWidth('min(92vw, 28.75rem)')
|
|
77
|
+
.padding('0.375rem')
|
|
78
|
+
.borderRadius(palette.radii.md)
|
|
79
|
+
.border('1px solid transparent')
|
|
80
|
+
.boxShadow(palette.effect.softShadow)
|
|
81
|
+
|
|
82
|
+
styleBuilder
|
|
83
|
+
.select('.tabs__overflow--open .tabs__overflow-menu', theme)
|
|
84
|
+
.display('grid')
|
|
85
|
+
.gap('0.25rem')
|
|
86
|
+
|
|
87
|
+
styleBuilder
|
|
88
|
+
.select('.tabs__overflow-option', theme)
|
|
89
|
+
.width('100%')
|
|
90
|
+
.padding('0.4em 0.6em')
|
|
91
|
+
.textAlign('left')
|
|
92
|
+
.whiteSpace('nowrap')
|
|
93
|
+
.apply(palette.applyFont(palette.font.size.xxs, palette.font.weight.w600))
|
|
94
|
+
|
|
95
|
+
styleBuilder.select('.tabs__overflow-option:hover', theme)
|
|
96
|
+
|
|
97
|
+
styleBuilder
|
|
98
|
+
.select('.tabs__select-wrap', theme)
|
|
99
|
+
.display('none')
|
|
100
|
+
.margin('0.125rem 0 0.25rem')
|
|
101
|
+
|
|
102
|
+
styleBuilder
|
|
103
|
+
.select('.tabs__select', theme)
|
|
104
|
+
.display('block')
|
|
105
|
+
.width('100%')
|
|
106
|
+
.minHeight('2.625rem')
|
|
107
|
+
.padding('0.625rem 2.5rem 0.625rem 0.75rem')
|
|
108
|
+
.borderRadius(palette.radii.md)
|
|
109
|
+
.border('1px solid transparent')
|
|
110
|
+
.apply(palette.applyFont(palette.font.size.xs, palette.font.weight.w600))
|
|
111
|
+
.appearance('none')
|
|
112
|
+
.webkitAppearance('none')
|
|
113
|
+
.mozAppearance('none')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function registerTabsControlStyles(theme: ThemeMode, palette: ThemePalette) {
|
|
117
|
+
styleBuilder
|
|
118
|
+
.select('.tabs__control', theme)
|
|
119
|
+
.position('absolute')
|
|
120
|
+
.width('1px')
|
|
121
|
+
.height('1px')
|
|
122
|
+
.opacity('0')
|
|
123
|
+
.pointerEvents('none')
|
|
124
|
+
|
|
125
|
+
styleBuilder
|
|
126
|
+
.select('.tabs__tab, .tabs__tab-buttons > .btn', theme)
|
|
127
|
+
.display('inline-flex')
|
|
128
|
+
.alignItems('center')
|
|
129
|
+
.justifyContent('center')
|
|
130
|
+
.padding('0.5em 0.75em')
|
|
131
|
+
.apply(palette.applyFont(palette.font.size.xxs, palette.font.weight.w700))
|
|
132
|
+
.gap('0.5em')
|
|
133
|
+
.overflow('hidden')
|
|
134
|
+
.textAlign('center')
|
|
135
|
+
.cursor('pointer')
|
|
136
|
+
.transition(
|
|
137
|
+
'background 160ms ease, color 160ms ease, border-color 160ms ease',
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
styleBuilder.select('.tabs__tab', theme).gridRow('1')
|
|
141
|
+
|
|
142
|
+
styleBuilder
|
|
143
|
+
.select('.tabs__tab-buttons > .btn', theme)
|
|
144
|
+
.flexShrink('0')
|
|
145
|
+
.whiteSpace('nowrap')
|
|
146
|
+
|
|
147
|
+
styleBuilder.select('.tabs__tab-buttons > .btn:hover', theme)
|
|
148
|
+
|
|
149
|
+
styleBuilder.select('.tabs__tab-buttons > .btn.active', theme)
|
|
150
|
+
|
|
151
|
+
styleBuilder
|
|
152
|
+
.select('.tabs__tab-icon', theme)
|
|
153
|
+
.display('inline-flex')
|
|
154
|
+
.alignItems('center')
|
|
155
|
+
.justifyContent('center')
|
|
156
|
+
.width('1.7em')
|
|
157
|
+
.height('1.7em')
|
|
158
|
+
.flexShrink('0')
|
|
159
|
+
|
|
160
|
+
styleBuilder
|
|
161
|
+
.select('.tabs__tab-icon svg', theme)
|
|
162
|
+
.display('block')
|
|
163
|
+
.width('100%')
|
|
164
|
+
.height('100%')
|
|
165
|
+
|
|
166
|
+
styleBuilder.select('.tabs__tab-label', theme).display('inline-block')
|
|
167
|
+
styleBuilder
|
|
168
|
+
.select('.tabs__tab-label', theme)
|
|
169
|
+
.overflow('hidden')
|
|
170
|
+
.textOverflow('ellipsis')
|
|
171
|
+
.whiteSpace('nowrap')
|
|
172
|
+
.maxWidth('100%')
|
|
173
|
+
|
|
174
|
+
styleBuilder
|
|
175
|
+
.select('.tabs__control:disabled + .tabs__tab, .tabs__tab--disabled', theme)
|
|
176
|
+
.opacity('0.45')
|
|
177
|
+
.cursor('not-allowed')
|
|
178
|
+
|
|
179
|
+
styleBuilder
|
|
180
|
+
.select('.tabs__tab-buttons > .btn:disabled', theme)
|
|
181
|
+
.opacity('0.45')
|
|
182
|
+
.cursor('not-allowed')
|
|
183
|
+
|
|
184
|
+
styleBuilder
|
|
185
|
+
.select('.tabs__control:focus-visible + .tabs__tab', theme)
|
|
186
|
+
.outline('2px solid currentColor')
|
|
187
|
+
.outlineOffset('2px')
|
|
188
|
+
|
|
189
|
+
styleBuilder
|
|
190
|
+
.select('.tabs__tab-buttons > .btn[hidden]', theme)
|
|
191
|
+
.display('none')
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function registerTabsPanelStyles(theme: ThemeMode) {
|
|
195
|
+
styleBuilder
|
|
196
|
+
.select('.tabs__panel', theme)
|
|
197
|
+
.gridRow('2')
|
|
198
|
+
.gridColumn('1 / -1')
|
|
199
|
+
.display('none')
|
|
200
|
+
.minWidth('0')
|
|
201
|
+
.padding('0')
|
|
202
|
+
|
|
203
|
+
styleBuilder
|
|
204
|
+
.select('.tabs__control:checked + .tabs__tab + .tabs__panel', theme)
|
|
205
|
+
.display('block')
|
|
206
|
+
|
|
207
|
+
styleBuilder
|
|
208
|
+
.select(
|
|
209
|
+
'.tabs__list:not(:has(.tabs__control:checked)) .tabs__item:first-child .tabs__panel',
|
|
210
|
+
theme,
|
|
211
|
+
)
|
|
212
|
+
.display('block')
|
|
213
|
+
|
|
214
|
+
styleBuilder.select('.tabs__panel-body', theme)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function registerTabsResponsiveStyles(theme: ThemeMode) {
|
|
218
|
+
styleBuilder.select('.tabs--enhanced .tabs__tabs-row', theme).display('flex')
|
|
219
|
+
|
|
220
|
+
styleBuilder
|
|
221
|
+
.select('.tabs--compact .tabs__select-wrap', theme)
|
|
222
|
+
.display('block')
|
|
223
|
+
|
|
224
|
+
styleBuilder.select('.tabs--enhanced .tabs__tab', theme).display('none')
|
|
225
|
+
|
|
226
|
+
styleBuilder.select('.tabs--compact .tabs__tabs-row', theme).display('none')
|
|
227
|
+
|
|
228
|
+
styleBuilder
|
|
229
|
+
.select('.tabs--compact .tabs__list', theme)
|
|
230
|
+
.gridTemplateColumns('1fr')
|
|
231
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineComponent, html } from 'regor'
|
|
2
|
+
|
|
3
|
+
export interface ThemeToggle {}
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One icon button that flips between the light and dark themes. The theme
|
|
7
|
+
* stylesheets decide which glyph shows, so the markup carries no state; the
|
|
8
|
+
* theme runtime binds every [data-theme-toggle] and keeps aria-pressed in sync.
|
|
9
|
+
*/
|
|
10
|
+
const themeToggleTemplate = html`<button
|
|
11
|
+
class="theme-toggle"
|
|
12
|
+
type="button"
|
|
13
|
+
aria-label="Dark theme"
|
|
14
|
+
aria-pressed="false"
|
|
15
|
+
data-theme-toggle
|
|
16
|
+
>
|
|
17
|
+
<Icon
|
|
18
|
+
class="theme-toggle__glyph theme-toggle__glyph--light"
|
|
19
|
+
name="lucide:sun"
|
|
20
|
+
aria-hidden="true"/>
|
|
21
|
+
<Icon
|
|
22
|
+
class="theme-toggle__glyph theme-toggle__glyph--dark"
|
|
23
|
+
name="lucide:moon"
|
|
24
|
+
aria-hidden="true"/>
|
|
25
|
+
</button>`
|
|
26
|
+
|
|
27
|
+
function defineThemeToggleComponent() {
|
|
28
|
+
return defineComponent<ThemeToggle>(themeToggleTemplate)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function defineThemeToggleComponents() {
|
|
32
|
+
return { themeToggle: defineThemeToggleComponent() }
|
|
33
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
styleBuilder,
|
|
3
|
+
type ThemeMode,
|
|
4
|
+
type ThemePalette,
|
|
5
|
+
themes,
|
|
6
|
+
} from '@purestack/ts-style'
|
|
7
|
+
|
|
8
|
+
const GLYPH_MOTION =
|
|
9
|
+
'transform 360ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 200ms ease'
|
|
10
|
+
|
|
11
|
+
export function registerThemeToggleStyles() {
|
|
12
|
+
themes.forEach((theme, palette) => {
|
|
13
|
+
registerThemeToggleButtonStyles(theme, palette)
|
|
14
|
+
registerThemeToggleGlyphStyles(theme)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function registerThemeToggleButtonStyles(
|
|
19
|
+
theme: ThemeMode,
|
|
20
|
+
palette: ThemePalette,
|
|
21
|
+
) {
|
|
22
|
+
styleBuilder
|
|
23
|
+
.select('.theme-toggle', theme)
|
|
24
|
+
.display('inline-grid')
|
|
25
|
+
.placeItems('center')
|
|
26
|
+
.flexShrink('0')
|
|
27
|
+
.width('2.625rem')
|
|
28
|
+
.height('2.625rem')
|
|
29
|
+
.padding('0')
|
|
30
|
+
.borderRadius(palette.radii.pill)
|
|
31
|
+
.border('1px solid transparent')
|
|
32
|
+
.background('transparent')
|
|
33
|
+
.color(palette.current.text.subtle)
|
|
34
|
+
.cursor('pointer')
|
|
35
|
+
.transition('background 160ms ease, color 160ms ease')
|
|
36
|
+
|
|
37
|
+
styleBuilder
|
|
38
|
+
.select('.theme-toggle:hover', theme)
|
|
39
|
+
.background(palette.current.surface.hover.background)
|
|
40
|
+
.borderColor(palette.current.surface.hover.border)
|
|
41
|
+
.color(palette.current.text.default)
|
|
42
|
+
|
|
43
|
+
styleBuilder
|
|
44
|
+
.select('.theme-toggle:active', theme)
|
|
45
|
+
.background(palette.current.surface.active.background)
|
|
46
|
+
|
|
47
|
+
styleBuilder
|
|
48
|
+
.select('.theme-toggle:focus-visible', theme)
|
|
49
|
+
.outline(`2px solid ${palette.current.border.focus}`)
|
|
50
|
+
.outlineOffset('2px')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Both glyphs share one grid cell. The theme in use shows its own glyph and
|
|
55
|
+
* tucks the other away rotated, so a switch reads as one turning motion.
|
|
56
|
+
*/
|
|
57
|
+
function registerThemeToggleGlyphStyles(theme: ThemeMode) {
|
|
58
|
+
const shown = theme === 'dark' ? 'dark' : 'light'
|
|
59
|
+
const hidden = theme === 'dark' ? 'light' : 'dark'
|
|
60
|
+
const hiddenTurn = theme === 'dark' ? '90deg' : '-90deg'
|
|
61
|
+
|
|
62
|
+
styleBuilder
|
|
63
|
+
.select('.theme-toggle .theme-toggle__glyph', theme)
|
|
64
|
+
.gridArea('1 / 1')
|
|
65
|
+
.width('1.25rem')
|
|
66
|
+
.height('1.25rem')
|
|
67
|
+
|
|
68
|
+
styleBuilder
|
|
69
|
+
.select(`.theme-toggle .theme-toggle__glyph--${shown}`, theme)
|
|
70
|
+
.opacity(1)
|
|
71
|
+
.transform('rotate(0deg) scale(1)')
|
|
72
|
+
|
|
73
|
+
styleBuilder
|
|
74
|
+
.select(`.theme-toggle .theme-toggle__glyph--${hidden}`, theme)
|
|
75
|
+
.opacity(0)
|
|
76
|
+
.transform(`rotate(${hiddenTurn}) scale(0.5)`)
|
|
77
|
+
|
|
78
|
+
// Animate only once the runtime has settled the first theme, so a page
|
|
79
|
+
// never loads with the glyphs mid-turn.
|
|
80
|
+
styleBuilder
|
|
81
|
+
.select(':scope[data-theme-ready] .theme-toggle__glyph', theme)
|
|
82
|
+
.transition(GLYPH_MOTION)
|
|
83
|
+
|
|
84
|
+
styleBuilder
|
|
85
|
+
.select(':scope[data-theme-ready] .theme-toggle__glyph', theme)
|
|
86
|
+
.media('prefers-reduced-motion: reduce')
|
|
87
|
+
.transition('none')
|
|
88
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { defineComponent, html, type SRef } from 'regor'
|
|
2
|
+
import type { ToastItem, ToastStore } from './toastStore'
|
|
3
|
+
|
|
4
|
+
export interface ToastHost {
|
|
5
|
+
items: SRef<ToastItem[]>
|
|
6
|
+
dismiss: ToastStore['dismiss']
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const toastHostTemplate = html`<Flex
|
|
10
|
+
direction="column"
|
|
11
|
+
align="center"
|
|
12
|
+
r-if="items.length > 0"
|
|
13
|
+
style="position: fixed; top: 1rem; left: 50%; z-index: 1200; max-width: calc(100vw - 2rem); transform: translateX(-50%); pointer-events: none;"
|
|
14
|
+
>
|
|
15
|
+
<Panel
|
|
16
|
+
class="py-0"
|
|
17
|
+
r-for="toast in items"
|
|
18
|
+
:tone="toast.tone"
|
|
19
|
+
variant="surface"
|
|
20
|
+
style="max-width: calc(100vw - 2rem); pointer-events: auto;"
|
|
21
|
+
>
|
|
22
|
+
<Flex align="center" justify="between" wrap="true">
|
|
23
|
+
<span>{{ toast.message }}</span>
|
|
24
|
+
<Btn
|
|
25
|
+
size="sm"
|
|
26
|
+
variant="subtleBtn"
|
|
27
|
+
icon="iconoir:xmark"
|
|
28
|
+
@click="dismiss(toast.id)"
|
|
29
|
+
>
|
|
30
|
+
Dismiss
|
|
31
|
+
</Btn>
|
|
32
|
+
</Flex>
|
|
33
|
+
</Panel>
|
|
34
|
+
</Flex>`
|
|
35
|
+
|
|
36
|
+
export function defineToastComponents() {
|
|
37
|
+
return {
|
|
38
|
+
toastHost: defineComponent<ToastHost>(toastHostTemplate, {
|
|
39
|
+
props: ['items', 'dismiss'],
|
|
40
|
+
}),
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SemanticTone } from '@purestack/ts-style'
|
|
2
|
+
import { sref } from 'regor'
|
|
3
|
+
|
|
4
|
+
export interface ToastItem {
|
|
5
|
+
id: string
|
|
6
|
+
message: string
|
|
7
|
+
tone: SemanticTone
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const TOAST_TIMEOUT_MS: Partial<Record<SemanticTone, number>> = {
|
|
11
|
+
info: 5000,
|
|
12
|
+
success: 5000,
|
|
13
|
+
warning: 10000,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class ToastStore {
|
|
17
|
+
readonly items = sref<ToastItem[]>([])
|
|
18
|
+
private nextId = 0
|
|
19
|
+
private readonly timers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
20
|
+
|
|
21
|
+
notify = (text: string, tone: SemanticTone = 'info') => {
|
|
22
|
+
if (!text) return
|
|
23
|
+
const toast: ToastItem = {
|
|
24
|
+
id: `toast-${++this.nextId}`,
|
|
25
|
+
message: text,
|
|
26
|
+
tone,
|
|
27
|
+
}
|
|
28
|
+
this.items([...this.items(), toast])
|
|
29
|
+
const timeout = TOAST_TIMEOUT_MS[tone]
|
|
30
|
+
if (timeout) {
|
|
31
|
+
this.timers.set(
|
|
32
|
+
toast.id,
|
|
33
|
+
setTimeout(() => this.dismiss(toast.id), timeout),
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dismiss = (id: string) => {
|
|
39
|
+
const timer = this.timers.get(id)
|
|
40
|
+
if (timer) clearTimeout(timer)
|
|
41
|
+
this.timers.delete(id)
|
|
42
|
+
this.items(this.items().filter((toast) => toast.id !== id))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
clear = () => {
|
|
46
|
+
for (const timer of this.timers.values()) clearTimeout(timer)
|
|
47
|
+
this.timers.clear()
|
|
48
|
+
this.items([])
|
|
49
|
+
}
|
|
50
|
+
}
|