@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.
Files changed (41) hide show
  1. package/dist/analytics-sink-app-insights/app-insights-sink.ts +13 -3
  2. package/dist/analytics-sink-posthog/client-sdk-sink.ts +12 -1
  3. package/dist/analytics-sink-posthog/replay.ts +10 -1
  4. package/dist/astro-calendar/Calendar.astro +7 -2
  5. package/dist/astro-date-picker/DatePicker.astro +10 -2
  6. package/dist/astro-file-tree/FileTree.astro +86 -0
  7. package/dist/astro-file-tree/FileTree.stories.ts +29 -2
  8. package/dist/astro-file-tree/FileTreeItems.astro +77 -0
  9. package/dist/astro-file-tree/index.ts +2 -4
  10. package/dist/astro-pagination/Pagination.astro +100 -1
  11. package/dist/astro-pagination/Pagination.stories.ts +11 -1
  12. package/dist/astro-pagination/index.ts +1 -1
  13. package/dist/astro-skip-to-content/SkipToContent.astro +22 -1
  14. package/dist/astro-skip-to-content/SkipToContent.stories.ts +7 -1
  15. package/dist/astro-skip-to-content/index.ts +1 -1
  16. package/dist/astro-slider/Slider.astro +75 -0
  17. package/dist/astro-slider/Slider.stories.ts +24 -0
  18. package/dist/astro-slider/index.ts +1 -1
  19. package/dist/astro-steps/Step.astro +20 -0
  20. package/dist/astro-steps/StepContent.astro +17 -0
  21. package/dist/astro-steps/StepDescription.astro +17 -0
  22. package/dist/astro-steps/StepIndicator.astro +20 -0
  23. package/dist/astro-steps/StepTitle.astro +17 -0
  24. package/dist/astro-steps/Steps.astro +17 -1
  25. package/dist/astro-steps/Steps.stories.ts +7 -1
  26. package/dist/astro-steps/index.ts +6 -1
  27. package/dist/astro-toast/Toaster.astro +3 -3
  28. package/dist/avatar-group/avatar-group.styles.ts +4 -4
  29. package/dist/badge/badge.styles.ts +2 -2
  30. package/dist/callout/callout.styles.ts +3 -3
  31. package/dist/empty-state/empty-state.styles.ts +2 -2
  32. package/dist/file-tree/file-tree.styles.ts +31 -0
  33. package/dist/file-tree/file-tree.ts +360 -0
  34. package/dist/file-tree/index.ts +15 -0
  35. package/dist/input/input.styles.ts +1 -1
  36. package/dist/presence-indicator/presence-indicator.styles.ts +4 -4
  37. package/dist/progress-display/progress-display.styles.ts +2 -2
  38. package/dist/status-indicator/status-indicator.styles.ts +8 -8
  39. package/dist/toast/toast.styles.ts +3 -6
  40. package/package.json +1 -1
  41. 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('posthog-js')) as unknown as
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('posthog-js')) as unknown as
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 ?? new Date(),
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
- /** Date format string (ignored by native input, but kept for interface compatibility) */
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 './astro-file-tree.astro'
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
- // `.astro` single-file components have no wildcard surface — re-export the
2
- // default export by name. (The astro-meta build used to paper over the broken
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
- <div></div>
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
- <div></div>
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>