@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.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/dist/ts-components.d.mts +1662 -0
  4. package/dist/ts-components.d.mts.map +1 -0
  5. package/dist/ts-components.mjs +9200 -0
  6. package/dist/ts-components.mjs.map +1 -0
  7. package/package.json +130 -0
  8. package/src/defineComponents.ts +87 -0
  9. package/src/index.ts +270 -0
  10. package/src/registerStyles.ts +63 -0
  11. package/src/standard/alertBox/alertBox.ts +64 -0
  12. package/src/standard/autoId.ts +25 -0
  13. package/src/standard/badge/badge.ts +41 -0
  14. package/src/standard/badge/badgeStyle.ts +21 -0
  15. package/src/standard/barChart/barChart.ts +338 -0
  16. package/src/standard/barChart/barChartStyle.ts +81 -0
  17. package/src/standard/btn/btn.ts +191 -0
  18. package/src/standard/btn/btnStyle.ts +59 -0
  19. package/src/standard/btnGroup/btnGroup.ts +208 -0
  20. package/src/standard/btnGroup/btnGroupStyle.ts +85 -0
  21. package/src/standard/chart/cartesianChart.ts +99 -0
  22. package/src/standard/chart/chartDefaults.ts +25 -0
  23. package/src/standard/chart/chartUtils.ts +84 -0
  24. package/src/standard/classicLogo/classicLogo.ts +429 -0
  25. package/src/standard/classicLogo/classicLogoStyle.ts +292 -0
  26. package/src/standard/componentVariant.ts +153 -0
  27. package/src/standard/composer/composer.ts +1035 -0
  28. package/src/standard/composer/composerCanvasInitialStyle.ts +225 -0
  29. package/src/standard/composer/composerCanvasRevertStyle.ts +14 -0
  30. package/src/standard/composer/composerCanvasSharedStyle.ts +60 -0
  31. package/src/standard/composer/composerCanvasStyleTypes.ts +8 -0
  32. package/src/standard/composer/composerCanvasThemeStyle.ts +53 -0
  33. package/src/standard/composer/composerStyle.ts +156 -0
  34. package/src/standard/composer/emailHtmlPolicy.ts +474 -0
  35. package/src/standard/consent/consent.ts +173 -0
  36. package/src/standard/consent/consentStyle.ts +44 -0
  37. package/src/standard/contactForm/contactForm.ts +135 -0
  38. package/src/standard/contactForm/contactFormStyle.ts +116 -0
  39. package/src/standard/doughnutChart/doughnutChart.ts +527 -0
  40. package/src/standard/doughnutChart/doughnutChartStyle.ts +64 -0
  41. package/src/standard/dropFiles/dropFiles.ts +480 -0
  42. package/src/standard/dropFiles/dropFilesStyle.ts +171 -0
  43. package/src/standard/expandablePanel/expandablePanel.ts +106 -0
  44. package/src/standard/expandablePanel/expandablePanelStyle.ts +56 -0
  45. package/src/standard/flex/flex.ts +269 -0
  46. package/src/standard/flex/flexStyle.ts +556 -0
  47. package/src/standard/footer/footer.ts +93 -0
  48. package/src/standard/footer/footerStyle.ts +39 -0
  49. package/src/standard/form/autoCompleteInput.ts +567 -0
  50. package/src/standard/form/form.ts +205 -0
  51. package/src/standard/form/formCheck.ts +96 -0
  52. package/src/standard/form/formInputField.ts +184 -0
  53. package/src/standard/form/formSelectField.ts +152 -0
  54. package/src/standard/form/formStyle.ts +585 -0
  55. package/src/standard/form/multiAutoCompleteInput.ts +880 -0
  56. package/src/standard/grid/grid.ts +177 -0
  57. package/src/standard/grid/gridStyle.ts +56 -0
  58. package/src/standard/icon/icon.ts +96 -0
  59. package/src/standard/icon/iconStyle.ts +50 -0
  60. package/src/standard/landing/codeShowcase.ts +54 -0
  61. package/src/standard/landing/comparisonColumn.ts +30 -0
  62. package/src/standard/landing/comparisonFeature.ts +22 -0
  63. package/src/standard/landing/comparisonTable.ts +38 -0
  64. package/src/standard/landing/ctaSection.ts +59 -0
  65. package/src/standard/landing/featureCard.ts +41 -0
  66. package/src/standard/landing/landing.ts +52 -0
  67. package/src/standard/landing/landingBand.ts +217 -0
  68. package/src/standard/landing/landingSection.ts +43 -0
  69. package/src/standard/landing/landingTypes.ts +141 -0
  70. package/src/standard/landing/metricItem.ts +25 -0
  71. package/src/standard/landing/metricStrip.ts +20 -0
  72. package/src/standard/lineChart/lineChart.ts +462 -0
  73. package/src/standard/lineChart/lineChartStyle.ts +91 -0
  74. package/src/standard/logo/logo.ts +192 -0
  75. package/src/standard/logo/logoStyle.ts +164 -0
  76. package/src/standard/modal/modal.ts +171 -0
  77. package/src/standard/modal/modalStore.ts +154 -0
  78. package/src/standard/modal/modalStyle.ts +190 -0
  79. package/src/standard/navMenu/navMenu.ts +278 -0
  80. package/src/standard/navMenu/navMenuStyle.ts +323 -0
  81. package/src/standard/pageLinks/pageLinks.ts +90 -0
  82. package/src/standard/pageScript/pageScript.ts +155 -0
  83. package/src/standard/pageToc/pageToc.ts +154 -0
  84. package/src/standard/pageToc/pageTocStyle.ts +372 -0
  85. package/src/standard/panel/panel.ts +55 -0
  86. package/src/standard/panel/panelStyle.ts +15 -0
  87. package/src/standard/pricing/pricing.ts +146 -0
  88. package/src/standard/searchBox/searchBox.ts +54 -0
  89. package/src/standard/searchBox/searchBoxStyle.ts +180 -0
  90. package/src/standard/sectionHeader/sectionHeader.ts +99 -0
  91. package/src/standard/signIn/signIn.ts +167 -0
  92. package/src/standard/signIn/signInStyle.ts +70 -0
  93. package/src/standard/tabs/tabs.ts +193 -0
  94. package/src/standard/tabs/tabsStyle.ts +231 -0
  95. package/src/standard/themeToggle/themeToggle.ts +33 -0
  96. package/src/standard/themeToggle/themeToggleStyle.ts +88 -0
  97. package/src/standard/toast/toastHost.ts +42 -0
  98. package/src/standard/toast/toastStore.ts +50 -0
  99. package/src/standard/topBar/topBar.ts +101 -0
  100. package/src/standard/topBar/topBarStyle.ts +169 -0
  101. package/src/standard/virtualList/variableVirtualTable.ts +371 -0
  102. package/src/standard/virtualList/virtualList.ts +423 -0
  103. package/src/standard/virtualList/virtualListStyle.ts +34 -0
  104. package/src/standard/virtualList/virtualTable.ts +179 -0
  105. package/src/standard/virtualList/virtualTableStyle.ts +24 -0
  106. package/tsconfig.json +23 -0
@@ -0,0 +1,146 @@
1
+ import type { SemanticTone } from '@purestack/ts-style'
2
+ import { defineComponent, html, type RefOrValue } from 'regor'
3
+ import type {
4
+ ComponentVariant,
5
+ ComponentVariantMode,
6
+ } from '../componentVariant'
7
+
8
+ export interface PricingTable {
9
+ eyebrow?: RefOrValue<string>
10
+ title?: RefOrValue<string>
11
+ subtitle?: RefOrValue<string>
12
+ footnote?: RefOrValue<string>
13
+ tone?: RefOrValue<SemanticTone>
14
+ variant?: RefOrValue<ComponentVariant>
15
+ variantMode?: RefOrValue<ComponentVariantMode>
16
+ columns?: RefOrValue<number | string>
17
+ columnsLg?: RefOrValue<number | string>
18
+ columnsXl?: RefOrValue<number | string>
19
+ }
20
+
21
+ export interface PricingPlan {
22
+ title?: RefOrValue<string>
23
+ summary?: RefOrValue<string>
24
+ price?: RefOrValue<string>
25
+ period?: RefOrValue<string>
26
+ badge?: RefOrValue<string>
27
+ note?: RefOrValue<string>
28
+ variant?: RefOrValue<ComponentVariant>
29
+ variantMode?: RefOrValue<ComponentVariantMode>
30
+ icon?: RefOrValue<string>
31
+ ctaLabel?: RefOrValue<string>
32
+ ctaLink?: RefOrValue<string>
33
+ tone?: RefOrValue<SemanticTone>
34
+ }
35
+
36
+ export interface PricingFeature {
37
+ icon?: RefOrValue<string>
38
+ tone?: RefOrValue<SemanticTone>
39
+ variant?: RefOrValue<ComponentVariant>
40
+ }
41
+
42
+ const pricingTableTemplate = html`<Panel :tone="tone" :variant="variant || 'none'" :variantMode="variantMode">
43
+ <Flex direction="column" align="start">
44
+ <SectionHeader
45
+ r-if="eyebrow || title || subtitle || footnote"
46
+ :eyebrow="eyebrow"
47
+ :title="title"
48
+ :subtitle="subtitle"
49
+ :footnote="footnote"
50
+ titleTag="h2"/>
51
+ <Grid
52
+ :columns="columns || 1"
53
+ :columnsLg="columnsLg || 2"
54
+ :columnsXl="columnsXl || 4"
55
+ alignItems="stretch"
56
+ justifyItems="center"
57
+ >
58
+ <slot></slot>
59
+ </Grid>
60
+ </Flex>
61
+ </Panel>`
62
+
63
+ const pricingPlanTemplate = html`<Panel :tone="tone" :variant="variant || 'surface'" :variantMode="variantMode">
64
+ <Flex direction="column" align="start">
65
+ <Flex justify="between" align="center" r-if="icon || badge">
66
+ <IconFrame
67
+ r-if="icon"
68
+ :name="icon"
69
+ :tone="tone"
70
+ variant="surface"
71
+ size="lg"/>
72
+ <Badge r-if="badge" :tone="tone">{{ badge }}</Badge>
73
+ </Flex>
74
+ <SectionHeader :title="title" :subtitle="summary" titleTag="h3"/>
75
+ <Flex align="baseline" r-if="price">
76
+ <strong class="fs-h2">{{ price }}</strong>
77
+ <span r-if="period">{{ period }}</span>
78
+ </Flex>
79
+ <BtnLink :href="ctaLink" :tone="tone" r-if="ctaLabel && ctaLink">
80
+ {{ ctaLabel }}
81
+ </BtnLink>
82
+ <Flex container="ul" direction="column" align="start" class="m-0 p-0">
83
+ <slot></slot>
84
+ </Flex>
85
+ <small r-if="note">{{ note }}</small>
86
+ </Flex>
87
+ </Panel>`
88
+
89
+ const pricingFeatureTemplate = html`<Flex container="li" align="start">
90
+ <IconFrame
91
+ :name="icon || 'iconoir:check'"
92
+ :tone="tone"
93
+ :variant="variant || 'surface'"
94
+ size="sm"/>
95
+ <slot></slot>
96
+ </Flex>`
97
+
98
+ function definePricingTableComponent() {
99
+ return defineComponent<PricingTable>(pricingTableTemplate, {
100
+ props: [
101
+ 'eyebrow',
102
+ 'title',
103
+ 'subtitle',
104
+ 'footnote',
105
+ 'tone',
106
+ 'variant',
107
+ 'variantMode',
108
+ 'columns',
109
+ 'columnsLg',
110
+ 'columnsXl',
111
+ ],
112
+ })
113
+ }
114
+
115
+ function definePricingPlanComponent() {
116
+ return defineComponent<PricingPlan>(pricingPlanTemplate, {
117
+ props: [
118
+ 'title',
119
+ 'summary',
120
+ 'price',
121
+ 'period',
122
+ 'badge',
123
+ 'note',
124
+ 'variant',
125
+ 'variantMode',
126
+ 'icon',
127
+ 'ctaLabel',
128
+ 'ctaLink',
129
+ 'tone',
130
+ ],
131
+ })
132
+ }
133
+
134
+ function definePricingFeatureComponent() {
135
+ return defineComponent<PricingFeature>(pricingFeatureTemplate, {
136
+ props: ['icon', 'tone', 'variant'],
137
+ })
138
+ }
139
+
140
+ export function definePricingComponents() {
141
+ return {
142
+ pricingTable: definePricingTableComponent(),
143
+ pricingPlan: definePricingPlanComponent(),
144
+ pricingFeature: definePricingFeatureComponent(),
145
+ }
146
+ }
@@ -0,0 +1,54 @@
1
+ import type { TsSsgContext } from '@purestack/ts-common'
2
+
3
+ import { resolveTsSsgContext } from '@purestack/ts-common'
4
+ import type { SemanticTone } from '@purestack/ts-style'
5
+ import { defineComponent, html, type RefOrValue } from 'regor'
6
+ import type { ComponentVariant } from '../componentVariant'
7
+
8
+ export interface SearchBox {
9
+ placeholder?: RefOrValue<string>
10
+ tone?: RefOrValue<SemanticTone>
11
+ variant?: RefOrValue<ComponentVariant>
12
+ }
13
+
14
+ const searchBoxTemplate = html`<div class="site-search" data-pagefind-search>
15
+ <FormInputField
16
+ icon="tabler:search"
17
+ :tone="tone"
18
+ :variant="variant"
19
+ type="search"
20
+ name="q"
21
+ :placeholder="placeholder"
22
+ autocomplete="off"
23
+ spellcheck="false"
24
+ data-pagefind-input
25
+ aria-label="Search site content"/>
26
+ <div
27
+ class="site-search__results doc-content"
28
+ data-pagefind-results
29
+ hidden
30
+ ></div>
31
+ </div>`
32
+
33
+ function defineSearchBoxComponent() {
34
+ return defineComponent<SearchBox>(searchBoxTemplate, {
35
+ props: ['tone', 'variant', 'placeholder'],
36
+ context: (head) => ({
37
+ tone: head.props.tone,
38
+ variant: head.props.variant,
39
+ placeholder: resolveSearchPlaceholder(resolveTsSsgContext(head)),
40
+ }),
41
+ })
42
+ }
43
+
44
+ export function defineSearchComponents() {
45
+ return { searchBox: defineSearchBoxComponent() }
46
+ }
47
+
48
+ function resolveSearchPlaceholder(context: TsSsgContext): string {
49
+ const title = context?.site?.siteTitle
50
+ if (typeof title !== 'string') return 'Search'
51
+ const trimmed = title.trim()
52
+ if (trimmed.length === 0) return 'Search'
53
+ return `Search ${trimmed}`
54
+ }
@@ -0,0 +1,180 @@
1
+ import type { ThemePalette } from '@purestack/ts-style'
2
+ import {
3
+ BREAKPOINTS,
4
+ getBreakpoint,
5
+ mediaAbove,
6
+ styleBuilder,
7
+ type ThemeMode,
8
+ themes,
9
+ } from '@purestack/ts-style'
10
+
11
+ export function registerSearchBoxStyles() {
12
+ themes.forEach((theme, palette) => {
13
+ registerSearchBoxShellStyles(theme)
14
+ registerSearchBoxResultStyles(theme, palette)
15
+ })
16
+ }
17
+
18
+ export function registerSearchBoxShellStyles(theme: ThemeMode) {
19
+ registerSearchBoxShellContainerStyles(theme)
20
+ registerSearchBoxShellAccessibilityStyles(theme)
21
+ }
22
+
23
+ function registerSearchBoxShellContainerStyles(theme: ThemeMode) {
24
+ styleBuilder
25
+ .select('.site-search', theme)
26
+ .position('relative')
27
+ .maxWidth('100%')
28
+
29
+ styleBuilder.select('.site-search__field', theme).display('block')
30
+ }
31
+
32
+ function registerSearchBoxShellAccessibilityStyles(theme: ThemeMode) {
33
+ styleBuilder
34
+ .select('.site-search__sr-only', theme)
35
+ .position('absolute')
36
+ .width('1px')
37
+ .height('1px')
38
+ .padding('0')
39
+ .margin('-1px')
40
+ .overflow('hidden')
41
+ .clip('rect(0, 0, 0, 0)')
42
+ .whiteSpace('nowrap')
43
+ .border('0')
44
+ }
45
+
46
+ export function registerSearchBoxResultStyles(
47
+ theme: ThemeMode,
48
+ palette: ThemePalette,
49
+ ) {
50
+ registerSearchBoxResultContainerStyles(theme, palette)
51
+ registerSearchBoxResultTopBarOverlayStyles(theme)
52
+ registerSearchBoxResultListStyles(theme, palette)
53
+ registerSearchBoxResultContentStyles(theme, palette)
54
+ }
55
+
56
+ function registerSearchBoxResultContainerStyles(
57
+ theme: ThemeMode,
58
+ palette: ThemePalette,
59
+ ) {
60
+ styleBuilder
61
+ .select('.site-search__results', theme)
62
+ .position('absolute')
63
+ .top('calc(100% + 0.5rem)')
64
+ .right('0')
65
+ .width('min(35rem, calc(100vw - 2rem))')
66
+ .maxHeight('26.25rem')
67
+ .overflow('auto')
68
+ .padding('0.5rem')
69
+ .borderRadius(palette.radii.lg)
70
+ .border(`1px solid ${palette.current.border.default}`)
71
+ .background(palette.current.surface.rest.background)
72
+ .boxShadow(palette.effect.strongShadow)
73
+ .zIndex('60')
74
+ }
75
+
76
+ function registerSearchBoxResultTopBarOverlayStyles(theme: ThemeMode) {
77
+ styleBuilder
78
+ .select('.topbar .site-search__results', theme)
79
+ .media(mediaAbove(BREAKPOINTS.sm))
80
+ .position('fixed')
81
+ .top('4.75rem')
82
+ .left('50%')
83
+ .right('auto')
84
+ .transform('translateX(-50%)')
85
+ .width(`min(${getBreakpoint(BREAKPOINTS.sm)}, calc(100vw - 2rem))`)
86
+ .zIndex('120')
87
+ }
88
+
89
+ function registerSearchBoxResultListStyles(
90
+ theme: ThemeMode,
91
+ palette: ThemePalette,
92
+ ) {
93
+ styleBuilder
94
+ .select('.site-search__results ul.site-search__list', theme)
95
+ .listStyle('none')
96
+ .margin('0')
97
+ .padding('0')
98
+ .display('grid')
99
+ .gap('0.5em')
100
+
101
+ styleBuilder
102
+ .select('.site-search__results .site-search__item', theme)
103
+ .margin('0')
104
+ .padding('0')
105
+
106
+ styleBuilder
107
+ .select('.site-search__results .site-search__link', theme)
108
+ .display('grid')
109
+ .gap('0.375rem')
110
+ .padding('0.75rem 0.875rem')
111
+ .borderRadius(palette.radii.md)
112
+ .border(`1px solid ${palette.current.border.subtle}`)
113
+ .background(palette.current.surface.rest.background)
114
+ .textDecoration('none')
115
+ .color(palette.current.text.default)
116
+ .transition(
117
+ 'border-color 160ms ease, background-color 160ms ease, transform 160ms ease',
118
+ )
119
+
120
+ styleBuilder
121
+ .select('.site-search__results .site-search__link:hover', theme)
122
+ .background(palette.current.surfaceAlt.rest.background)
123
+ .borderColor(palette.current.border.default)
124
+
125
+ styleBuilder
126
+ .select('.site-search__results .site-search__link:focus-visible', theme)
127
+ .outline(`2px solid ${palette.current.border.focus}`)
128
+ }
129
+
130
+ function registerSearchBoxResultContentStyles(
131
+ theme: ThemeMode,
132
+ palette: ThemePalette,
133
+ ) {
134
+ styleBuilder
135
+ .select('.site-search__results .site-search__title', theme)
136
+ .display('block')
137
+ .apply(palette.applyFont(palette.font.size.body, palette.font.weight.w700))
138
+ .color(palette.current.text.default)
139
+ .wordBreak('break-word')
140
+
141
+ styleBuilder
142
+ .select('.site-search__results .site-search__excerpt', theme)
143
+ .display('block')
144
+ .margin('0')
145
+ .apply(palette.applyFont(palette.font.size.sm))
146
+ .color(palette.current.text.subtle)
147
+ .wordBreak('break-word')
148
+ .display('-webkit-box')
149
+ .lineClamp('3')
150
+ .webkitBoxOrient('vertical')
151
+ .overflow('hidden')
152
+
153
+ styleBuilder
154
+ .select('.site-search__results mark.site-search__highlight', theme)
155
+ .padding('0 0.1875rem')
156
+ .borderRadius('0.3125rem')
157
+ .background(palette.current.button.hover.background)
158
+ .color(palette.current.button.hover.text)
159
+
160
+ styleBuilder
161
+ .select('.site-search__results .site-search__message', theme)
162
+ .margin('0')
163
+ .padding('0.625rem 0.75rem')
164
+ .borderRadius(palette.radii.md)
165
+ .border(`1px dashed ${palette.current.border.default}`)
166
+ .background(palette.current.surface.rest.background)
167
+ .color(palette.current.text.subtle)
168
+
169
+ styleBuilder
170
+ .select('.site-search__results .site-search__message--error', theme)
171
+ .border(`1px solid ${palette.semanticTone.danger.surface.rest.border}`)
172
+ .background(palette.semanticTone.danger.surface.rest.background)
173
+ .color(palette.semanticTone.danger.surface.rest.text)
174
+
175
+ styleBuilder
176
+ .select('.site-search__results .site-search__message--empty', theme)
177
+ .border(`1px solid ${palette.semanticTone.warning.surface.rest.border}`)
178
+ .background(palette.semanticTone.warning.surface.rest.background)
179
+ .color(palette.semanticTone.warning.surface.rest.text)
180
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ type ComputedRef,
3
+ computed,
4
+ defineComponent,
5
+ html,
6
+ type RefOrValue,
7
+ unref,
8
+ } from 'regor'
9
+
10
+ export interface SectionHeader {
11
+ eyebrow?: RefOrValue<string>
12
+ title?: RefOrValue<string>
13
+ subtitle?: RefOrValue<string>
14
+ footnote?: RefOrValue<string>
15
+ titleTag?: RefOrValue<string>
16
+ titleId?: RefOrValue<string>
17
+ eyebrowClass?: RefOrValue<string>
18
+ titleClass?: RefOrValue<string>
19
+ subtitleClass?: RefOrValue<string>
20
+ footnoteClass?: RefOrValue<string>
21
+ resolvedTitleTag?: ComputedRef<string>
22
+ }
23
+
24
+ const sectionHeaderTemplate = html`<div class="section-header">
25
+ <div class="text-eyebrow" :class="eyebrowClass" r-if="eyebrow">
26
+ {{ eyebrow }}
27
+ </div>
28
+ <div
29
+ :is="resolvedTitleTag"
30
+ :id="titleId"
31
+ class="text-title"
32
+ :class="titleClass"
33
+ r-if="title"
34
+ >
35
+ {{ title }}
36
+ </div>
37
+ <p class="text-tagline" :class="subtitleClass" r-if="subtitle">
38
+ {{ subtitle }}
39
+ </p>
40
+ <p class="prose-meta" :class="footnoteClass" r-if="footnote">
41
+ {{ footnote }}
42
+ </p>
43
+ <slot></slot>
44
+ </div>`
45
+
46
+ function defineSectionHeaderComponent() {
47
+ return defineComponent<SectionHeader>(sectionHeaderTemplate, {
48
+ props: [
49
+ 'eyebrow',
50
+ 'title',
51
+ 'subtitle',
52
+ 'footnote',
53
+ 'titleTag',
54
+ 'titleId',
55
+ 'eyebrowClass',
56
+ 'titleClass',
57
+ 'subtitleClass',
58
+ 'footnoteClass',
59
+ ],
60
+ context: (head) => resolveSectionHeader(head.props),
61
+ })
62
+ }
63
+
64
+ export function defineSectionHeaderComponents() {
65
+ return {
66
+ sectionHeader: defineSectionHeaderComponent(),
67
+ }
68
+ }
69
+
70
+ function resolveSectionHeader(props: SectionHeader): SectionHeader {
71
+ return {
72
+ ...props,
73
+ resolvedTitleTag: computed(() => resolveTitleTag(unref(props.titleTag))),
74
+ }
75
+ }
76
+
77
+ function resolveTitleTag(value: unknown): string {
78
+ const normalized = resolveString(value).toLowerCase()
79
+ if (
80
+ normalized === 'h1' ||
81
+ normalized === 'h2' ||
82
+ normalized === 'h3' ||
83
+ normalized === 'h4' ||
84
+ normalized === 'h5' ||
85
+ normalized === 'h6' ||
86
+ normalized === 'p' ||
87
+ normalized === 'div' ||
88
+ normalized === 'span'
89
+ ) {
90
+ return normalized
91
+ }
92
+ return 'h2'
93
+ }
94
+
95
+ function resolveString(value: unknown) {
96
+ return typeof value === 'string' && value.trim().length > 0
97
+ ? value.trim()
98
+ : ''
99
+ }
@@ -0,0 +1,167 @@
1
+ import { tryResolveTsSsgContext } from '@purestack/ts-common'
2
+ import type { SemanticTone } from '@purestack/ts-style'
3
+ import {
4
+ type ComputedRef,
5
+ computed,
6
+ defineComponent,
7
+ html,
8
+ type RefOrValue,
9
+ unref,
10
+ } from 'regor'
11
+ import type {
12
+ ComponentVariant,
13
+ ComponentVariantMode,
14
+ } from '../componentVariant'
15
+
16
+ export interface SignIn {
17
+ avatarSrc?: RefOrValue<string>
18
+ avatarAlt?: RefOrValue<string>
19
+ icon?: RefOrValue<string>
20
+ accountIcon?: RefOrValue<string>
21
+ label?: RefOrValue<string>
22
+ tone?: RefOrValue<SemanticTone>
23
+ variant?: RefOrValue<ComponentVariant>
24
+ variantMode?: RefOrValue<ComponentVariantMode>
25
+ authEnabled?: boolean
26
+ signUpEnabled?: boolean
27
+ resolvedIcon?: ComputedRef<string>
28
+ resolvedAccountIcon?: ComputedRef<string>
29
+ resolvedLabel?: ComputedRef<string>
30
+ resolvedAvatarAlt?: ComputedRef<string>
31
+ }
32
+
33
+ const DEFAULT_SIGN_IN_ICON = 'lucide:log-in'
34
+ const DEFAULT_ACCOUNT_ICON = 'tabler:user-filled'
35
+ const DEFAULT_SIGN_IN_LABEL = 'Account'
36
+
37
+ const signInTemplate = html`<details
38
+ r-if="authEnabled"
39
+ class="sign-in position-relative"
40
+ data-menu-runtime
41
+ data-menu-align="end"
42
+ >
43
+ <summary
44
+ class="sign-in__trigger topbar__icon rounded-pill cursor-pointer overflow-hidden"
45
+ aria-label="Account menu"
46
+ >
47
+ <span
48
+ class="sign-in__signed-out-view tone-fill-surface-hover tone-fill-surface-active tone-border-surface-hover tone-border-surface-active"
49
+ aria-hidden="true"
50
+ >
51
+ <Icon :name="resolvedIcon"/>
52
+ </span>
53
+ <span
54
+ class="sign-in__signed-in-view tone-fill-surface-hover tone-fill-surface-active tone-border-surface-hover tone-border-surface-active"
55
+ aria-hidden="true"
56
+ >
57
+ <slot name="avatar">
58
+ <img
59
+ r-if="avatarSrc"
60
+ class="sign-in__avatar w-full h-full rounded-pill"
61
+ :src="avatarSrc"
62
+ :alt="resolvedAvatarAlt"/>
63
+ <Icon r-else :name="resolvedAccountIcon"/>
64
+ </slot>
65
+ </span>
66
+ <span class="hidden">{{ resolvedLabel }}</span>
67
+ </summary>
68
+ <Panel
69
+ class="sign-in__panel p-1 mt-1"
70
+ :tone="tone"
71
+ :variant="variant || 'surfaceAlt'"
72
+ :variantMode="variantMode"
73
+ data-menu-panel
74
+ >
75
+ <slot>
76
+ <Flex
77
+ container="nav"
78
+ direction="column"
79
+ align="stretch"
80
+ class="gap-1"
81
+ aria-label="Account"
82
+ >
83
+ <BtnLink
84
+ class="sign-in__signed-out-action justify-start w-full text-start"
85
+ href="/signin/"
86
+ :tone="tone"
87
+ variant="subtleBtn"
88
+ icon="lucide:log-in"
89
+ >
90
+ Sign in
91
+ </BtnLink>
92
+ <BtnLink
93
+ r-if="signUpEnabled"
94
+ class="sign-in__signed-out-action justify-start w-full text-start"
95
+ href="/signup/"
96
+ :tone="tone"
97
+ variant="subtleBtn"
98
+ icon="lucide:user-plus"
99
+ >
100
+ Sign up
101
+ </BtnLink>
102
+ <BtnLink
103
+ class="sign-in__signed-in-action justify-start w-full text-start"
104
+ href="/account/"
105
+ :tone="tone"
106
+ variant="subtleBtn"
107
+ icon="lucide:user-round"
108
+ >
109
+ Account
110
+ </BtnLink>
111
+ <BtnLink
112
+ class="sign-in__signed-in-action justify-start w-full text-start"
113
+ href="/account/settings/"
114
+ :tone="tone"
115
+ variant="subtleBtn"
116
+ icon="lucide:settings"
117
+ >
118
+ Settings
119
+ </BtnLink>
120
+ <BtnLink
121
+ class="sign-in__signed-in-action justify-start w-full text-start"
122
+ href="/signout/"
123
+ :tone="tone"
124
+ variant="subtleBtn"
125
+ icon="lucide:log-out"
126
+ >
127
+ Sign out
128
+ </BtnLink>
129
+ </Flex>
130
+ </slot>
131
+ </Panel>
132
+ </details>`
133
+
134
+ function defineSignInComponent() {
135
+ return defineComponent<SignIn>(signInTemplate, {
136
+ props: [
137
+ 'avatarSrc',
138
+ 'avatarAlt',
139
+ 'icon',
140
+ 'accountIcon',
141
+ 'label',
142
+ 'tone',
143
+ 'variant',
144
+ 'variantMode',
145
+ ],
146
+ context: (head) => resolveSignIn(head.props, head),
147
+ })
148
+ }
149
+
150
+ export function defineSignInComponents() {
151
+ return { signIn: defineSignInComponent() }
152
+ }
153
+
154
+ function resolveSignIn(props: SignIn, head: unknown): SignIn {
155
+ const auth = tryResolveTsSsgContext(head)?.site?.auth
156
+ return {
157
+ ...props,
158
+ authEnabled: auth?.enabled === true,
159
+ signUpEnabled: auth?.signUp === true,
160
+ resolvedIcon: computed(() => unref(props.icon) || DEFAULT_SIGN_IN_ICON),
161
+ resolvedAccountIcon: computed(
162
+ () => unref(props.accountIcon) || DEFAULT_ACCOUNT_ICON,
163
+ ),
164
+ resolvedLabel: computed(() => unref(props.label) || DEFAULT_SIGN_IN_LABEL),
165
+ resolvedAvatarAlt: computed(() => unref(props.avatarAlt) || ''),
166
+ }
167
+ }