@refraction-ui/astro 0.17.3 → 0.18.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/dist/analytics-sink-app-insights/app-insights-sink.ts +13 -3
- package/dist/analytics-sink-posthog/client-sdk-sink.ts +12 -1
- package/dist/analytics-sink-posthog/replay.ts +10 -1
- package/dist/astro-calendar/Calendar.astro +7 -2
- package/dist/astro-date-picker/DatePicker.astro +10 -2
- package/dist/astro-file-tree/FileTree.astro +86 -0
- package/dist/astro-file-tree/FileTree.stories.ts +29 -2
- package/dist/astro-file-tree/FileTreeItems.astro +77 -0
- package/dist/astro-file-tree/index.ts +2 -4
- package/dist/astro-pagination/Pagination.astro +100 -1
- package/dist/astro-pagination/Pagination.stories.ts +11 -1
- package/dist/astro-pagination/index.ts +1 -1
- package/dist/astro-skip-to-content/SkipToContent.astro +22 -1
- package/dist/astro-skip-to-content/SkipToContent.stories.ts +7 -1
- package/dist/astro-skip-to-content/index.ts +1 -1
- package/dist/astro-slider/Slider.astro +75 -0
- package/dist/astro-slider/Slider.stories.ts +24 -0
- package/dist/astro-slider/index.ts +1 -1
- package/dist/astro-steps/Step.astro +20 -0
- package/dist/astro-steps/StepContent.astro +17 -0
- package/dist/astro-steps/StepDescription.astro +17 -0
- package/dist/astro-steps/StepIndicator.astro +20 -0
- package/dist/astro-steps/StepTitle.astro +17 -0
- package/dist/astro-steps/Steps.astro +17 -1
- package/dist/astro-steps/Steps.stories.ts +7 -1
- package/dist/astro-steps/index.ts +6 -1
- package/dist/astro-toast/Toaster.astro +3 -3
- package/dist/avatar-group/avatar-group.styles.ts +4 -4
- package/dist/badge/badge.styles.ts +2 -2
- package/dist/callout/callout.styles.ts +3 -3
- package/dist/empty-state/empty-state.styles.ts +2 -2
- package/dist/file-tree/file-tree.styles.ts +31 -0
- package/dist/file-tree/file-tree.ts +360 -0
- package/dist/file-tree/index.ts +15 -0
- package/dist/input/input.styles.ts +1 -1
- package/dist/presence-indicator/presence-indicator.styles.ts +4 -4
- package/dist/progress-display/progress-display.styles.ts +2 -2
- package/dist/status-indicator/status-indicator.styles.ts +8 -8
- package/dist/toast/toast.styles.ts +3 -6
- package/package.json +1 -1
- package/dist/astro-file-tree/astro-file-tree.astro +0 -4
|
@@ -98,6 +98,18 @@ const DEFAULT_INGEST_ENDPOINT = 'https://dc.services.visualstudio.com'
|
|
|
98
98
|
const NO_RETRY = new Set([400, 401, 403, 413])
|
|
99
99
|
const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms))
|
|
100
100
|
|
|
101
|
+
// `@microsoft/applicationinsights-web` is an OPTIONAL peer — installed only by
|
|
102
|
+
// consumers who opt into client-sdk mode. The specifier must stay
|
|
103
|
+
// non-statically-analyzable: the meta packages (`@refraction-ui/astro`,
|
|
104
|
+
// `@refraction-ui/react`) re-export this module from their entry, so a literal
|
|
105
|
+
// `import('@microsoft/applicationinsights-web')` makes consumer bundlers
|
|
106
|
+
// (vite/rollup) fail the whole build trying to resolve a package that is not
|
|
107
|
+
// installed — long before tree-shaking can drop the module. Routing the
|
|
108
|
+
// specifier through a const keeps the import runtime-only. (The previous
|
|
109
|
+
// `/* @vite-ignore */` annotation did NOT suffice — vite still resolves
|
|
110
|
+
// literal dynamic-import specifiers in SSR builds.)
|
|
111
|
+
const appInsightsWebSpecifier = '@microsoft/applicationinsights-web'
|
|
112
|
+
|
|
101
113
|
/* ------------------------------------------------------------------ */
|
|
102
114
|
/* client-sdk mode */
|
|
103
115
|
/* ------------------------------------------------------------------ */
|
|
@@ -119,9 +131,7 @@ function createClientSdkSink(opts: ClientSdkOptions): AnalyticsSink {
|
|
|
119
131
|
return instance
|
|
120
132
|
}
|
|
121
133
|
// Lazy, dynamic, optional — never a static/hard dependency.
|
|
122
|
-
const mod = (await import(
|
|
123
|
-
/* @vite-ignore */ '@microsoft/applicationinsights-web'
|
|
124
|
-
)) as {
|
|
134
|
+
const mod = (await import(appInsightsWebSpecifier)) as {
|
|
125
135
|
ApplicationInsights: new (cfg: {
|
|
126
136
|
config: {
|
|
127
137
|
connectionString?: string
|
|
@@ -55,9 +55,20 @@ export interface PostHogClientSdkSinkOptions {
|
|
|
55
55
|
loadPostHog?: () => Promise<{ default: PostHogJs } | PostHogJs>
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// `posthog-js` is an OPTIONAL peer — installed only by consumers who opt into
|
|
59
|
+
// client-sdk mode. The specifier must stay non-statically-analyzable: the meta
|
|
60
|
+
// packages (`@refraction-ui/astro`, `@refraction-ui/react`) re-export this
|
|
61
|
+
// module from their entry, so a literal `import('posthog-js')` makes consumer
|
|
62
|
+
// bundlers (vite/rollup) fail the whole build trying to resolve a package that
|
|
63
|
+
// is not installed — long before tree-shaking can drop the module. Routing the
|
|
64
|
+
// specifier through a const keeps the import runtime-only. (A `/* @vite-ignore */`
|
|
65
|
+
// annotation does NOT suffice — vite still resolves literal dynamic-import
|
|
66
|
+
// specifiers in SSR builds.)
|
|
67
|
+
const posthogJsSpecifier = 'posthog-js'
|
|
68
|
+
|
|
58
69
|
async function defaultLoad(): Promise<PostHogJs> {
|
|
59
70
|
// Dynamic import keeps `posthog-js` out of the graph until first delivery.
|
|
60
|
-
const mod = (await import(
|
|
71
|
+
const mod = (await import(posthogJsSpecifier)) as unknown as
|
|
61
72
|
| { default: PostHogJs }
|
|
62
73
|
| PostHogJs
|
|
63
74
|
return 'default' in mod ? mod.default : mod
|
|
@@ -68,8 +68,17 @@ export interface SessionReplayHandle {
|
|
|
68
68
|
stop(): void
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// `posthog-js` is an OPTIONAL peer — installed only by consumers who opt into
|
|
72
|
+
// session replay. The specifier must stay non-statically-analyzable: the meta
|
|
73
|
+
// packages (`@refraction-ui/astro`, `@refraction-ui/react`) re-export this
|
|
74
|
+
// module from their entry, so a literal `import('posthog-js')` makes consumer
|
|
75
|
+
// bundlers (vite/rollup) fail the whole build trying to resolve a package that
|
|
76
|
+
// is not installed — long before tree-shaking can drop the module. Routing the
|
|
77
|
+
// specifier through a const keeps the import runtime-only.
|
|
78
|
+
const posthogJsSpecifier = 'posthog-js'
|
|
79
|
+
|
|
71
80
|
async function defaultLoad(): Promise<PostHogReplay> {
|
|
72
|
-
const mod = (await import(
|
|
81
|
+
const mod = (await import(posthogJsSpecifier)) as unknown as
|
|
73
82
|
| { default: PostHogReplay }
|
|
74
83
|
| PostHogReplay
|
|
75
84
|
return 'default' in mod ? mod.default : mod
|
|
@@ -16,21 +16,26 @@ interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
|
16
16
|
maxDate?: string
|
|
17
17
|
/** Disabled date ISO strings */
|
|
18
18
|
disabledDates?: string[]
|
|
19
|
+
/** Reference "today" (ISO string) for isToday/aria-current and the initial
|
|
20
|
+
* month — inject to keep SSR and client hydration in agreement. */
|
|
21
|
+
today?: string
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
const { value, minDate, maxDate, disabledDates = [], class: className, ...props } = Astro.props
|
|
24
|
+
const { value, minDate, maxDate, disabledDates = [], today, class: className, ...props } = Astro.props
|
|
22
25
|
|
|
23
26
|
const parsedValue = value ? new Date(value) : undefined
|
|
24
27
|
const parsedMin = minDate ? new Date(minDate) : undefined
|
|
25
28
|
const parsedMax = maxDate ? new Date(maxDate) : undefined
|
|
26
29
|
const parsedDisabled = disabledDates.map((d: string) => new Date(d))
|
|
30
|
+
const parsedToday = today ? new Date(today) : undefined
|
|
27
31
|
|
|
28
32
|
const api = createCalendar({
|
|
29
33
|
value: parsedValue,
|
|
30
|
-
month: parsedValue ??
|
|
34
|
+
month: parsedValue ?? parsedToday,
|
|
31
35
|
minDate: parsedMin,
|
|
32
36
|
maxDate: parsedMax,
|
|
33
37
|
disabledDates: parsedDisabled,
|
|
38
|
+
today: parsedToday,
|
|
34
39
|
})
|
|
35
40
|
|
|
36
41
|
const MONTH_NAMES = [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { cn } from '../shared/index.ts'
|
|
3
|
-
import { datePickerTriggerStyles } from '../date-picker/index.ts'
|
|
3
|
+
import { datePickerTriggerStyles, formatDate } from '../date-picker/index.ts'
|
|
4
4
|
|
|
5
5
|
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
6
6
|
/** Selected date value (ISO string) */
|
|
@@ -11,7 +11,7 @@ interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
|
11
11
|
maxDate?: string
|
|
12
12
|
/** Show time selection inputs */
|
|
13
13
|
showTime?: boolean
|
|
14
|
-
/**
|
|
14
|
+
/** Display format for the selected-date text shown next to the input (e.g. 'DD/MM/YYYY'). The native input value itself stays ISO (`YYYY-MM-DD`). */
|
|
15
15
|
format?: string
|
|
16
16
|
/** Placeholder text */
|
|
17
17
|
placeholder?: string
|
|
@@ -49,6 +49,11 @@ function formatDateStr(isoStr?: string) {
|
|
|
49
49
|
const parsedValue = formatDateStr(value)
|
|
50
50
|
const parsedMin = formatDateStr(minDate)
|
|
51
51
|
const parsedMax = formatDateStr(maxDate)
|
|
52
|
+
|
|
53
|
+
// A custom display format cannot be honored by the native input itself (the
|
|
54
|
+
// browser locale controls its presentation), so the formatted date renders as
|
|
55
|
+
// adjacent text via the core's formatter — mirroring the React adapter.
|
|
56
|
+
const formattedDisplay = format && value ? formatDate(new Date(value), format, showTime) : undefined
|
|
52
57
|
---
|
|
53
58
|
|
|
54
59
|
<input
|
|
@@ -62,6 +67,9 @@ const parsedMax = formatDateStr(maxDate)
|
|
|
62
67
|
data-rfr-datepicker
|
|
63
68
|
{...props}
|
|
64
69
|
/>
|
|
70
|
+
{formattedDisplay && (
|
|
71
|
+
<span class="text-sm text-muted-foreground">{formattedDisplay}</span>
|
|
72
|
+
)}
|
|
65
73
|
|
|
66
74
|
<script>
|
|
67
75
|
document.querySelectorAll<HTMLInputElement>('[data-rfr-datepicker]').forEach((input) => {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
createFileTree,
|
|
4
|
+
fileTreeVariants,
|
|
5
|
+
type FileTreeNode,
|
|
6
|
+
} from '../file-tree/index.ts'
|
|
7
|
+
import { cn } from '../shared/index.ts'
|
|
8
|
+
import FileTreeItems from './FileTreeItems.astro'
|
|
9
|
+
|
|
10
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
11
|
+
/** Root-level nodes of the tree. */
|
|
12
|
+
nodes?: FileTreeNode[]
|
|
13
|
+
/** Expanded node ids (static SSR expansion state). */
|
|
14
|
+
expandedIds?: string[]
|
|
15
|
+
/** Initial expanded node ids (alias of `expandedIds` for uncontrolled usage). */
|
|
16
|
+
defaultExpandedIds?: string[]
|
|
17
|
+
/** Selected node id (`null` = none). */
|
|
18
|
+
selectedId?: string | null
|
|
19
|
+
/** Initial selected node id (alias of `selectedId` for uncontrolled usage). */
|
|
20
|
+
defaultSelectedId?: string | null
|
|
21
|
+
/** Accessible label for the tree (default `'File tree'`). */
|
|
22
|
+
'aria-label'?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
nodes = [],
|
|
27
|
+
expandedIds,
|
|
28
|
+
defaultExpandedIds,
|
|
29
|
+
selectedId,
|
|
30
|
+
defaultSelectedId,
|
|
31
|
+
class: className,
|
|
32
|
+
'aria-label': ariaLabel,
|
|
33
|
+
...props
|
|
34
|
+
} = Astro.props
|
|
35
|
+
|
|
36
|
+
// The headless core resolves the node model, expansion, selection, and the
|
|
37
|
+
// treeview ARIA objects; SSR renders the resulting visible rows statically.
|
|
38
|
+
const api = createFileTree({
|
|
39
|
+
nodes,
|
|
40
|
+
expandedIds: expandedIds ?? defaultExpandedIds,
|
|
41
|
+
selectedId: selectedId ?? defaultSelectedId,
|
|
42
|
+
'aria-label': ariaLabel,
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const state = api.getState()
|
|
46
|
+
const firstVisibleId = api.getVisibleItems()[0]?.node.id
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
<div class={cn(fileTreeVariants(), className)} {...props}>
|
|
50
|
+
<ul {...api.getTreeAria()} class="m-0 p-0" data-rfr-file-tree>
|
|
51
|
+
<FileTreeItems
|
|
52
|
+
nodes={state.nodes}
|
|
53
|
+
depth={1}
|
|
54
|
+
api={api}
|
|
55
|
+
expandedIds={state.expandedIds}
|
|
56
|
+
selectedId={state.selectedId}
|
|
57
|
+
firstVisibleId={firstVisibleId}
|
|
58
|
+
/>
|
|
59
|
+
</ul>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<script>
|
|
63
|
+
document.querySelectorAll<HTMLElement>('[data-rfr-file-tree]').forEach((root) => {
|
|
64
|
+
if (root.hasAttribute('data-rfr-file-tree-init')) return
|
|
65
|
+
root.setAttribute('data-rfr-file-tree-init', '')
|
|
66
|
+
|
|
67
|
+
// SSR-static component: expansion/selection are props. Activation is
|
|
68
|
+
// delivered as bubbling CustomEvents (`rfr-file-tree-select` /
|
|
69
|
+
// `rfr-file-tree-toggle`); the consumer re-renders with the new state.
|
|
70
|
+
root.addEventListener('click', (e) => {
|
|
71
|
+
const item = (e.target as HTMLElement).closest<HTMLElement>('[data-rfr-file-tree-item]')
|
|
72
|
+
if (!item || item.getAttribute('data-disabled') === 'true') return
|
|
73
|
+
const id = item.getAttribute('data-rfr-node-id')
|
|
74
|
+
if (!id) return
|
|
75
|
+
root.dispatchEvent(new CustomEvent('rfr-file-tree-select', { detail: { id }, bubbles: true }))
|
|
76
|
+
if (item.getAttribute('data-rfr-has-children') === 'true') {
|
|
77
|
+
root.dispatchEvent(
|
|
78
|
+
new CustomEvent('rfr-file-tree-toggle', {
|
|
79
|
+
detail: { id, expanded: item.getAttribute('aria-expanded') !== 'true' },
|
|
80
|
+
bubbles: true,
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
</script>
|
|
@@ -1,14 +1,41 @@
|
|
|
1
|
-
import Component from './
|
|
1
|
+
import Component from './FileTree.astro'
|
|
2
2
|
|
|
3
3
|
const meta = {
|
|
4
4
|
title: 'Astro/FileTree',
|
|
5
5
|
component: Component,
|
|
6
|
+
argTypes: {
|
|
7
|
+
nodes: { control: 'object' },
|
|
8
|
+
expandedIds: { control: 'object' },
|
|
9
|
+
selectedId: { control: 'text' },
|
|
10
|
+
},
|
|
6
11
|
}
|
|
7
12
|
|
|
8
13
|
export default meta
|
|
9
14
|
|
|
15
|
+
// Nodes must match the headless core's FileTreeNode shape
|
|
16
|
+
// ({ id, label, children?, disabled? }) — see
|
|
17
|
+
// packages/file-tree/src/file-tree.ts.
|
|
10
18
|
export const Default = {
|
|
11
19
|
args: {
|
|
12
|
-
|
|
20
|
+
nodes: [
|
|
21
|
+
{
|
|
22
|
+
id: 'src',
|
|
23
|
+
label: 'src',
|
|
24
|
+
children: [
|
|
25
|
+
{
|
|
26
|
+
id: 'src-components',
|
|
27
|
+
label: 'components',
|
|
28
|
+
children: [
|
|
29
|
+
{ id: 'src-components-button', label: 'Button.tsx' },
|
|
30
|
+
{ id: 'src-components-input', label: 'Input.tsx' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{ id: 'src-index', label: 'index.ts' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
{ id: 'package-json', label: 'package.json' },
|
|
37
|
+
],
|
|
38
|
+
expandedIds: ['src', 'src-components'],
|
|
39
|
+
selectedId: 'src-components-button',
|
|
13
40
|
}
|
|
14
41
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import {
|
|
3
|
+
fileTreeItemVariants,
|
|
4
|
+
fileTreeGlyphClass,
|
|
5
|
+
type FileTreeAPI,
|
|
6
|
+
type FileTreeNode,
|
|
7
|
+
type VisibleTreeItem,
|
|
8
|
+
} from '../file-tree/index.ts'
|
|
9
|
+
import FileTreeItems from './FileTreeItems.astro'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Recursive partial for `<FileTree>` — renders one depth level of the tree
|
|
13
|
+
* (`role="none"` list items wrapping `role="treeitem"` rows) and nests
|
|
14
|
+
* `role="group"` lists for expanded parents, mirroring the React adapter's
|
|
15
|
+
* markup. Not exported from the package entry; use `<FileTree>`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
nodes: FileTreeNode[]
|
|
20
|
+
/** 1-based nesting depth (ARIA `aria-level`). */
|
|
21
|
+
depth: number
|
|
22
|
+
api: FileTreeAPI
|
|
23
|
+
expandedIds: string[]
|
|
24
|
+
selectedId: string | null
|
|
25
|
+
firstVisibleId: string | undefined
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { nodes, depth, api, expandedIds, selectedId, firstVisibleId } = Astro.props
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
{nodes.map((node) => {
|
|
32
|
+
const hasChildren = Boolean(node.children && node.children.length > 0)
|
|
33
|
+
const isExpanded = hasChildren && expandedIds.includes(node.id)
|
|
34
|
+
const isSelected = selectedId === node.id
|
|
35
|
+
const item: VisibleTreeItem = {
|
|
36
|
+
node,
|
|
37
|
+
parentId: null, // unused by getItemAria; the core tracks real parents
|
|
38
|
+
depth,
|
|
39
|
+
hasChildren,
|
|
40
|
+
isExpanded,
|
|
41
|
+
isSelected,
|
|
42
|
+
isFocused: false, // SSR has no roving focus; the first visible item tabs in
|
|
43
|
+
}
|
|
44
|
+
const tabbable = node.id === firstVisibleId
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<li role="none" class="list-none">
|
|
48
|
+
<div
|
|
49
|
+
{...api.getItemAria(item)}
|
|
50
|
+
tabindex={tabbable ? 0 : -1}
|
|
51
|
+
data-disabled={node.disabled ? 'true' : undefined}
|
|
52
|
+
data-rfr-file-tree-item
|
|
53
|
+
data-rfr-node-id={node.id}
|
|
54
|
+
data-rfr-has-children={hasChildren ? 'true' : undefined}
|
|
55
|
+
style={`padding-left: ${(depth - 1) * 12}px`}
|
|
56
|
+
class={fileTreeItemVariants({ selected: isSelected ? 'true' : 'false' })}
|
|
57
|
+
>
|
|
58
|
+
<span aria-hidden="true" class={fileTreeGlyphClass}>
|
|
59
|
+
{hasChildren ? (isExpanded ? '▾' : '▸') : '·'}
|
|
60
|
+
</span>
|
|
61
|
+
<span class="truncate">{node.label}</span>
|
|
62
|
+
</div>
|
|
63
|
+
{hasChildren && isExpanded && (
|
|
64
|
+
<ul role="group" class="m-0 p-0">
|
|
65
|
+
<FileTreeItems
|
|
66
|
+
nodes={node.children!}
|
|
67
|
+
depth={depth + 1}
|
|
68
|
+
api={api}
|
|
69
|
+
expandedIds={expandedIds}
|
|
70
|
+
selectedId={selectedId}
|
|
71
|
+
firstVisibleId={firstVisibleId}
|
|
72
|
+
/>
|
|
73
|
+
</ul>
|
|
74
|
+
)}
|
|
75
|
+
</li>
|
|
76
|
+
)
|
|
77
|
+
})}
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// `export { default as FileTree } from './astro-file-tree.astro'` by rewriting it to exactly this.)
|
|
4
|
-
export { default as FileTree } from './astro-file-tree.astro'
|
|
1
|
+
export { default as FileTree } from './FileTree.astro'
|
|
2
|
+
export type { FileTreeNode } from '../file-tree/index.ts'
|
|
@@ -1,3 +1,102 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
createPagination,
|
|
4
|
+
paginationVariants,
|
|
5
|
+
paginationItemVariants,
|
|
6
|
+
paginationEllipsisClass,
|
|
7
|
+
} from '../pagination/index.ts'
|
|
8
|
+
import { cn } from '../shared/index.ts'
|
|
9
|
+
|
|
10
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
11
|
+
/** Current page, 1-based. Clamped into `[1, totalPages]` (default 1). */
|
|
12
|
+
page?: number
|
|
13
|
+
/** Total number of pages (default 1). */
|
|
14
|
+
totalPages?: number
|
|
15
|
+
/** Pages shown on each side of the current page (default 1). */
|
|
16
|
+
siblingCount?: number
|
|
17
|
+
/** Disables every control. */
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
page = 1,
|
|
23
|
+
totalPages = 1,
|
|
24
|
+
siblingCount = 1,
|
|
25
|
+
disabled = false,
|
|
26
|
+
class: className,
|
|
27
|
+
...props
|
|
28
|
+
} = Astro.props
|
|
29
|
+
|
|
30
|
+
const api = createPagination({ page, totalPages, siblingCount })
|
|
2
31
|
---
|
|
3
|
-
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
data-rfr-pagination
|
|
35
|
+
data-rfr-pagination-disabled={disabled ? '' : undefined}
|
|
36
|
+
class={cn(paginationVariants(), className)}
|
|
37
|
+
{...api.ariaProps}
|
|
38
|
+
{...api.dataAttributes}
|
|
39
|
+
{...props}
|
|
40
|
+
>
|
|
41
|
+
{Astro.slots.has('default') ? (
|
|
42
|
+
<slot />
|
|
43
|
+
) : (
|
|
44
|
+
<>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
{...api.getPreviousAria()}
|
|
48
|
+
disabled={disabled || !api.canGoPrevious}
|
|
49
|
+
class={paginationItemVariants()}
|
|
50
|
+
data-rfr-page={api.previousPage}
|
|
51
|
+
>
|
|
52
|
+
Previous
|
|
53
|
+
</button>
|
|
54
|
+
{api.items.map((item) =>
|
|
55
|
+
item.type === 'ellipsis' ? (
|
|
56
|
+
<span aria-hidden="true" class={paginationEllipsisClass}>…</span>
|
|
57
|
+
) : (
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
{...api.getPageAria(item.page)}
|
|
61
|
+
disabled={disabled}
|
|
62
|
+
data-state={item.page === api.page ? 'current' : 'default'}
|
|
63
|
+
class={paginationItemVariants({
|
|
64
|
+
state: item.page === api.page ? 'current' : 'default',
|
|
65
|
+
})}
|
|
66
|
+
data-rfr-page={item.page}
|
|
67
|
+
>
|
|
68
|
+
{item.page}
|
|
69
|
+
</button>
|
|
70
|
+
),
|
|
71
|
+
)}
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
{...api.getNextAria()}
|
|
75
|
+
disabled={disabled || !api.canGoNext}
|
|
76
|
+
class={paginationItemVariants()}
|
|
77
|
+
data-rfr-page={api.nextPage}
|
|
78
|
+
>
|
|
79
|
+
Next
|
|
80
|
+
</button>
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<script>
|
|
86
|
+
document.querySelectorAll<HTMLElement>('[data-rfr-pagination]').forEach((root) => {
|
|
87
|
+
if (root.hasAttribute('data-rfr-pagination-init')) return
|
|
88
|
+
root.setAttribute('data-rfr-pagination-init', '')
|
|
89
|
+
|
|
90
|
+
// SSR-static component: activation is delivered as a bubbling
|
|
91
|
+
// `rfr-page-change` CustomEvent; the consumer re-renders (or navigates)
|
|
92
|
+
// with the requested page. The DOM is never mutated here.
|
|
93
|
+
root.addEventListener('click', (e) => {
|
|
94
|
+
if (root.hasAttribute('data-rfr-pagination-disabled')) return
|
|
95
|
+
const btn = (e.target as HTMLElement).closest<HTMLButtonElement>('[data-rfr-page]')
|
|
96
|
+
if (!btn || btn.disabled) return
|
|
97
|
+
const page = parseInt(btn.getAttribute('data-rfr-page') || '', 10)
|
|
98
|
+
if (Number.isNaN(page) || String(page) === root.getAttribute('data-page')) return
|
|
99
|
+
root.dispatchEvent(new CustomEvent('rfr-page-change', { detail: { page }, bubbles: true }))
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
</script>
|
|
@@ -3,10 +3,20 @@ import Component from './Pagination.astro'
|
|
|
3
3
|
const meta = {
|
|
4
4
|
title: 'Astro/Pagination',
|
|
5
5
|
component: Component,
|
|
6
|
+
argTypes: {
|
|
7
|
+
page: { control: 'number' },
|
|
8
|
+
totalPages: { control: 'number' },
|
|
9
|
+
siblingCount: { control: 'number' },
|
|
10
|
+
disabled: { control: 'boolean' },
|
|
11
|
+
},
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
export default meta
|
|
9
15
|
|
|
10
16
|
export const Default = {
|
|
11
|
-
args: {
|
|
17
|
+
args: {
|
|
18
|
+
page: 5,
|
|
19
|
+
totalPages: 20,
|
|
20
|
+
siblingCount: 1,
|
|
21
|
+
},
|
|
12
22
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {}
|
|
1
|
+
export { default as Pagination } from './Pagination.astro'
|
|
@@ -1,3 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
+
import {
|
|
3
|
+
createSkipToContent,
|
|
4
|
+
skipToContentVariants,
|
|
5
|
+
} from '../skip-to-content/index.ts'
|
|
6
|
+
import { cn } from '../shared/index.ts'
|
|
7
|
+
|
|
8
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
9
|
+
/** The target element ID to skip to (without the #) */
|
|
10
|
+
targetId?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { targetId = 'main-content', class: className, ...props } = Astro.props
|
|
14
|
+
const api = createSkipToContent()
|
|
2
15
|
---
|
|
3
|
-
|
|
16
|
+
|
|
17
|
+
<a
|
|
18
|
+
href={`#${targetId}`}
|
|
19
|
+
class={cn(skipToContentVariants(), className)}
|
|
20
|
+
{...api.dataAttributes}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
<slot>Skip to content</slot>
|
|
24
|
+
</a>
|
|
@@ -3,10 +3,16 @@ import Component from './SkipToContent.astro'
|
|
|
3
3
|
const meta = {
|
|
4
4
|
title: 'Astro/SkipToContent',
|
|
5
5
|
component: Component,
|
|
6
|
+
argTypes: {
|
|
7
|
+
targetId: { control: 'text' },
|
|
8
|
+
},
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
export default meta
|
|
9
12
|
|
|
10
13
|
export const Default = {
|
|
11
|
-
args: {
|
|
14
|
+
args: {
|
|
15
|
+
default: 'Skip to content',
|
|
16
|
+
targetId: 'main-content',
|
|
17
|
+
},
|
|
12
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {}
|
|
1
|
+
export { default as SkipToContent } from './SkipToContent.astro'
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { createSlider } from '../slider/index.ts'
|
|
3
|
+
import { cn } from '../shared/index.ts'
|
|
4
|
+
|
|
5
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
6
|
+
/** Current value. Clamped into `[min, max]` and rounded to `step`. */
|
|
7
|
+
value?: number
|
|
8
|
+
/** Initial value when `value` is not given (defaults to `min`). */
|
|
9
|
+
defaultValue?: number
|
|
10
|
+
/** Minimum selectable value (default 0). */
|
|
11
|
+
min?: number
|
|
12
|
+
/** Maximum selectable value (default 100). */
|
|
13
|
+
max?: number
|
|
14
|
+
/** Granularity between selectable values (default 1). */
|
|
15
|
+
step?: number
|
|
16
|
+
/** Disables the control. */
|
|
17
|
+
disabled?: boolean
|
|
18
|
+
variant?: 'default'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
value,
|
|
23
|
+
defaultValue,
|
|
24
|
+
min = 0,
|
|
25
|
+
max = 100,
|
|
26
|
+
step = 1,
|
|
27
|
+
disabled,
|
|
28
|
+
variant: _variant,
|
|
29
|
+
class: className,
|
|
30
|
+
...props
|
|
31
|
+
} = Astro.props
|
|
32
|
+
|
|
33
|
+
// Native `<input type="range">` owns keyboard/pointer interaction; the core
|
|
34
|
+
// normalizes the rendered value and supplies the slider ARIA object.
|
|
35
|
+
const api = createSlider({
|
|
36
|
+
value: value ?? defaultValue ?? min,
|
|
37
|
+
min,
|
|
38
|
+
max,
|
|
39
|
+
step,
|
|
40
|
+
disabled,
|
|
41
|
+
'aria-label': props['aria-label'] as string | undefined,
|
|
42
|
+
})
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
<input
|
|
46
|
+
type="range"
|
|
47
|
+
data-rfr-slider
|
|
48
|
+
min={min}
|
|
49
|
+
max={max}
|
|
50
|
+
step={step}
|
|
51
|
+
value={api.value}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
class={cn('w-full', className)}
|
|
54
|
+
{...api.ariaProps}
|
|
55
|
+
{...api.dataAttributes}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
<script>
|
|
60
|
+
document.querySelectorAll<HTMLInputElement>('input[data-rfr-slider]').forEach((input) => {
|
|
61
|
+
if (input.hasAttribute('data-rfr-slider-init')) return
|
|
62
|
+
input.setAttribute('data-rfr-slider-init', '')
|
|
63
|
+
|
|
64
|
+
// The native range input owns keyboard/pointer interaction and its own
|
|
65
|
+
// DOM value; the change is delivered as a bubbling `rfr-value-change`
|
|
66
|
+
// CustomEvent (SSR-static adapter — consumers re-render with the value).
|
|
67
|
+
input.addEventListener('input', () => {
|
|
68
|
+
input.setAttribute('data-value', input.value)
|
|
69
|
+
input.setAttribute('aria-valuenow', input.value)
|
|
70
|
+
input.dispatchEvent(
|
|
71
|
+
new CustomEvent('rfr-value-change', { detail: { value: Number(input.value) }, bubbles: true }),
|
|
72
|
+
)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Component from './Slider.astro'
|
|
2
|
+
|
|
3
|
+
const meta = {
|
|
4
|
+
title: 'Astro/Slider',
|
|
5
|
+
component: Component,
|
|
6
|
+
argTypes: {
|
|
7
|
+
value: { control: 'number' },
|
|
8
|
+
min: { control: 'number' },
|
|
9
|
+
max: { control: 'number' },
|
|
10
|
+
step: { control: 'number' },
|
|
11
|
+
disabled: { control: 'boolean' },
|
|
12
|
+
},
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default meta
|
|
16
|
+
|
|
17
|
+
export const Default = {
|
|
18
|
+
args: {
|
|
19
|
+
value: 40,
|
|
20
|
+
min: 0,
|
|
21
|
+
max: 100,
|
|
22
|
+
step: 1,
|
|
23
|
+
},
|
|
24
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export {}
|
|
1
|
+
export { default as Slider } from './Slider.astro'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { createStep, stepVariants } from '../steps/index.ts'
|
|
3
|
+
import { cn } from '../shared/index.ts'
|
|
4
|
+
|
|
5
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
6
|
+
status?: 'completed' | 'active' | 'upcoming'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { status, class: className, ...props } = Astro.props
|
|
10
|
+
const api = createStep()
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
class={cn(stepVariants({ status }), className)}
|
|
15
|
+
data-state={status ?? 'upcoming'}
|
|
16
|
+
{...api.dataAttributes}
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|