@astronox/core 0.2.1 → 0.2.2

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 (64) hide show
  1. package/package.json +7 -1
  2. package/src/admin/contracts.ts +28 -0
  3. package/src/admin/index.ts +2 -0
  4. package/src/admin/module.ts +28 -0
  5. package/src/auth/contracts.ts +53 -0
  6. package/src/auth/firebase/client.ts +53 -0
  7. package/src/auth/firebase/contracts.ts +8 -0
  8. package/src/auth/firebase/index.ts +1 -0
  9. package/src/auth/index.ts +2 -0
  10. package/src/auth/module.ts +99 -0
  11. package/src/client/index.ts +3 -0
  12. package/src/client/preferences.ts +400 -0
  13. package/src/components/actions/Button/Button.astro +42 -6
  14. package/src/components/actions/Button/Button.css +19 -0
  15. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
  16. package/src/components/content/CardBody/CardBody.astro +3 -0
  17. package/src/components/content/CardBody/CardBody.css +10 -0
  18. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +549 -0
  19. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
  20. package/src/components/forms/Checkbox/Checkbox.astro +6 -0
  21. package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
  22. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
  23. package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
  24. package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
  25. package/src/components/forms/TextInput/TextInput.astro +7 -1
  26. package/src/components/forms/Textarea/Textarea.astro +32 -0
  27. package/src/components/forms/Textarea/Textarea.css +54 -0
  28. package/src/components/icons/BackIcon.astro +26 -0
  29. package/src/components/icons/DeleteIcon.astro +22 -0
  30. package/src/components/icons/StatusIndicatorIcon.astro +16 -0
  31. package/src/components/index.ts +10 -0
  32. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
  33. package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
  34. package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
  35. package/src/components/navigation/SubNavigation/SubNavigation.css +0 -5
  36. package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
  37. package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
  38. package/src/data/firestore/client/contracts.ts +55 -0
  39. package/src/data/firestore/client/firebase.ts +12 -0
  40. package/src/data/firestore/client/index.ts +3 -0
  41. package/src/data/firestore/client/repository.ts +111 -0
  42. package/src/display/contracts.ts +40 -0
  43. package/src/display/index.ts +1 -0
  44. package/src/display/tableProgressive.ts +62 -0
  45. package/src/patterns/Combobox/Combobox.astro +23 -0
  46. package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
  47. package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
  48. package/src/patterns/Modal/Modal.astro +1 -1
  49. package/src/patterns/Modal/Modal.css +38 -1
  50. package/src/patterns/Table/Table.astro +122 -7
  51. package/src/patterns/Table/TableBody.astro +8 -0
  52. package/src/patterns/Table/TableCell.css +6 -0
  53. package/src/patterns/Table/TableCellValue.astro +31 -0
  54. package/src/patterns/Table/TableFooter.astro +3 -1
  55. package/src/patterns/Table/TableHeader.astro +1 -0
  56. package/src/patterns/Table/TablePagination.astro +63 -47
  57. package/src/patterns/Table/TableSkeleton.astro +18 -22
  58. package/src/patterns/Table/TableSkeleton.css +30 -5
  59. package/src/patterns/Table/tableClientController.ts +253 -0
  60. package/src/patterns/Table/tableFilterController.ts +23 -31
  61. package/src/patterns/Table/tableProgressiveController.ts +615 -227
  62. package/src/patterns/Table/tableSortController.ts +25 -7
  63. package/src/styles/interaction.css +16 -0
  64. package/src/styles/style.css +2 -0
@@ -1,5 +1,9 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types';
3
+ import type {
4
+ PreferenceBinding,
5
+ PreferenceNumberBounds,
6
+ } from '../../client/preferences';
3
7
  import type { Tone } from '../../theme/contracts';
4
8
  import type {
5
9
  ComboboxOption,
@@ -15,6 +19,7 @@ type SharedProps = Omit<HTMLAttributes<'div'>, 'id'> & {
15
19
  id: string;
16
20
  label: string;
17
21
  disabled?: boolean;
22
+ preference?: PreferenceBinding;
18
23
  size?: ComboboxSize;
19
24
  tone?: Tone;
20
25
  width?: 'auto' | 'fill';
@@ -41,6 +46,7 @@ type NumberProps = SharedProps & {
41
46
  value: number;
42
47
  decrementLabel: string;
43
48
  incrementLabel: string;
49
+ preferenceBounds?: PreferenceNumberBounds;
44
50
  };
45
51
 
46
52
  type Props = OptionsProps | NumberProps;
@@ -130,6 +136,8 @@ if (mode === 'number') {
130
136
  'max',
131
137
  'min',
132
138
  'mode',
139
+ 'preference',
140
+ 'preferenceBounds',
133
141
  'size',
134
142
  'step',
135
143
  'tone',
@@ -185,6 +193,7 @@ if (mode === 'number') {
185
193
  'mode',
186
194
  'options',
187
195
  'placeholder',
196
+ 'preference',
188
197
  'removeLabel',
189
198
  'searchPlaceholder',
190
199
  'selectionMode',
@@ -233,6 +242,20 @@ const searchId = `${props.id}-search`;
233
242
  data-combobox-number-step={step}
234
243
  data-disabled={disabled ? true : undefined}
235
244
  data-tone={resolvedTone}
245
+ data-preference-id={props.preference?.id}
246
+ data-preference-path={props.preference?.path}
247
+ data-preference-control={
248
+ props.preference
249
+ ? mode === 'number'
250
+ ? 'combobox-number'
251
+ : 'combobox-options'
252
+ : undefined
253
+ }
254
+ data-preference-number-bounds={
255
+ mode === 'number' && (props as NumberProps).preferenceBounds
256
+ ? JSON.stringify((props as NumberProps).preferenceBounds)
257
+ : undefined
258
+ }
236
259
  >
237
260
  <span class="combobox__sizer" aria-hidden="true">
238
261
  {
@@ -6,8 +6,6 @@ import EnterFullscreenIcon from '../../components/icons/EnterFullscreenIcon.astr
6
6
  import ExitFullscreenIcon from '../../components/icons/ExitFullscreenIcon.astro';
7
7
  import ExternalLinkIcon from '../../components/icons/ExternalLinkIcon.astro';
8
8
 
9
- type FullscreenControlPlacement = 'inline' | 'overlay';
10
- type FullscreenOverlayOffset = 'default' | 'native-controls';
11
9
  type FullscreenUnsupportedFallback = {
12
10
  href: string;
13
11
  label: string;
@@ -17,8 +15,6 @@ type FullscreenUnsupportedFallback = {
17
15
  type Props = Omit<HTMLAttributes<'button'>, 'aria-controls' | 'type'> & {
18
16
  enterLabel: string;
19
17
  exitLabel: string;
20
- overlayOffset?: FullscreenOverlayOffset;
21
- placement?: FullscreenControlPlacement;
22
18
  targetId: string;
23
19
  tone?: Tone;
24
20
  unsupportedFallback?: FullscreenUnsupportedFallback;
@@ -28,8 +24,6 @@ const {
28
24
  class: className,
29
25
  enterLabel,
30
26
  exitLabel,
31
- overlayOffset = 'default',
32
- placement = 'inline',
33
27
  targetId,
34
28
  tone,
35
29
  unsupportedFallback,
@@ -44,10 +38,9 @@ const resolvedClass = ['fullscreen-control', className]
44
38
  {...attrs}
45
39
  class={resolvedClass}
46
40
  type="button"
47
- borderless={placement === 'inline'}
48
- full={placement === 'inline'}
41
+ borderless
42
+ full
49
43
  iconOnly
50
- size={placement === 'overlay' ? 'md' : undefined}
51
44
  tone={tone}
52
45
  aria-controls={targetId}
53
46
  aria-label={enterLabel}
@@ -56,8 +49,6 @@ const resolvedClass = ['fullscreen-control', className]
56
49
  data-fullscreen-control
57
50
  data-fullscreen-enter-label={enterLabel}
58
51
  data-fullscreen-exit-label={exitLabel}
59
- data-fullscreen-overlay-offset={overlayOffset}
60
- data-fullscreen-placement={placement}
61
52
  data-fullscreen-target={targetId}
62
53
  >
63
54
  <span class="fullscreen-control__icon" data-fullscreen-enter-icon>
@@ -73,10 +64,9 @@ const resolvedClass = ['fullscreen-control', className]
73
64
  <Button
74
65
  href={unsupportedFallback.href}
75
66
  target={unsupportedFallback.target ?? '_blank'}
76
- borderless={placement === 'inline'}
77
- full={placement === 'inline'}
67
+ borderless
68
+ full
78
69
  iconOnly
79
- size={placement === 'overlay' ? 'md' : undefined}
80
70
  tone={tone}
81
71
  aria-label={unsupportedFallback.label}
82
72
  title={unsupportedFallback.label}
@@ -15,32 +15,6 @@
15
15
  display: grid;
16
16
  }
17
17
 
18
- .fullscreen-control[data-fullscreen-placement='overlay'].nox-button {
19
- position: absolute;
20
- z-index: 2;
21
- display: none;
22
- inset-inline-end: var(--nox-space-3);
23
- inset-block-end: var(--nox-space-3);
24
- inline-size: var(--nox-section-responsive-block-size);
25
- block-size: var(--nox-section-responsive-block-size);
26
- min-block-size: 0;
27
- border-radius: var(--nox-radius-md);
28
- box-shadow: var(--nox-shadow-surface);
29
- }
30
-
31
- [data-fullscreen-region]:fullscreen
32
- .fullscreen-control[data-fullscreen-placement='overlay'].nox-button {
33
- display: inline-flex;
34
- }
35
-
36
- [data-fullscreen-region]:fullscreen
37
- .fullscreen-control[data-fullscreen-placement='overlay'][data-fullscreen-overlay-offset='native-controls'].nox-button {
38
- inset-block-end: calc(
39
- var(--nox-fullscreen-native-controls-block-size, 2.5rem) +
40
- var(--nox-space-3)
41
- );
42
- }
43
-
44
18
  [data-fullscreen-region]:fullscreen {
45
19
  inline-size: 100vw;
46
20
  block-size: 100vh;
@@ -4,7 +4,7 @@ import type { Tone } from '../../theme/contracts';
4
4
  import ModalClose from './ModalClose.astro';
5
5
 
6
6
  type ModalCloseControl = 'default' | 'delegated';
7
- type ModalSizing = 'content' | 'viewport';
7
+ type ModalSizing = 'adaptive' | 'confirmation' | 'content' | 'viewport';
8
8
 
9
9
  type Props = HTMLAttributes<'dialog'> & {
10
10
  id: string;
@@ -48,6 +48,33 @@ html:has(.modal[open]) [data-site-viewport] {
48
48
  overflow: auto;
49
49
  }
50
50
 
51
+ .modal[data-sizing='confirmation'] .modal__content {
52
+ inline-size: min(42rem, 100%);
53
+ block-size: auto;
54
+ max-block-size: 100%;
55
+ align-self: center;
56
+ overflow: auto;
57
+ }
58
+
59
+ .modal[data-sizing='adaptive'] .modal__content {
60
+ display: flex;
61
+ block-size: 100%;
62
+ min-block-size: 0;
63
+ flex-direction: column;
64
+ justify-content: center;
65
+ overflow: hidden;
66
+ }
67
+
68
+ .modal[data-sizing='adaptive']
69
+ .modal__content
70
+ > [data-composition='surface-group'] {
71
+ inline-size: 100%;
72
+ min-block-size: 0;
73
+ max-block-size: 100%;
74
+ flex: 0 1 auto;
75
+ overflow: hidden;
76
+ }
77
+
51
78
  .modal__default-close {
52
79
  position: absolute;
53
80
  z-index: 1;
@@ -81,7 +108,17 @@ html:has(.modal[open]) [data-site-viewport] {
81
108
  padding-inline: 0;
82
109
  }
83
110
 
84
- .modal__content [data-surface] {
111
+ .modal:not(
112
+ [data-sizing='adaptive'],
113
+ [data-sizing='confirmation']
114
+ )
115
+ .modal__content
116
+ [data-surface] {
85
117
  border-radius: 0;
86
118
  }
119
+
120
+ .modal:is([data-sizing='adaptive'], [data-sizing='confirmation'])
121
+ .modal__content {
122
+ padding: var(--nox-space-2);
123
+ }
87
124
  }
@@ -3,14 +3,17 @@ import type { HTMLAttributes } from 'astro/types';
3
3
  import type {
4
4
  TableColumn,
5
5
  TablePaginationModel,
6
+ TableProgressiveModel,
6
7
  TableRow,
7
8
  } from '../../display/contracts';
9
+ import { tableProgressiveBatchSize } from '../../display/tableProgressive';
8
10
  import type { Tone } from '../../theme/contracts';
9
11
  import SurfaceGroup from '../../composition/SurfaceGroup/SurfaceGroup.astro';
10
12
  import TableBody from './TableBody.astro';
11
13
  import TableFooter from './TableFooter.astro';
12
14
  import TableHeader from './TableHeader.astro';
13
15
  import TablePagination from './TablePagination.astro';
16
+ import TableSkeleton from './TableSkeleton.astro';
14
17
  import {
15
18
  getTableColumnDensity,
16
19
  getTableMinimumInlineSize,
@@ -33,6 +36,7 @@ type TablePaginationMessages = {
33
36
 
34
37
  type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
35
38
  caption: string;
39
+ clientProgressive?: boolean;
36
40
  columns: readonly TableColumn[];
37
41
  density?: 'regular' | 'compact';
38
42
  emptyMessage: string;
@@ -44,6 +48,8 @@ type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
44
48
  pagination?: TablePaginationModel;
45
49
  paginationKey?: string;
46
50
  paginationMessages?: TablePaginationMessages;
51
+ preparation?: 'automatic' | 'controlled';
52
+ progressive?: false | TableProgressiveModel;
47
53
  rowSizing?: 'content' | 'fill';
48
54
  rows: readonly TableRow[];
49
55
  skeletonRows?: number;
@@ -54,6 +60,7 @@ type Props = Omit<HTMLAttributes<'div'>, 'style'> & {
54
60
 
55
61
  const {
56
62
  caption,
63
+ clientProgressive = false,
57
64
  class: className,
58
65
  columns,
59
66
  'data-tone': dataTone,
@@ -67,6 +74,8 @@ const {
67
74
  pagination,
68
75
  paginationKey,
69
76
  paginationMessages,
77
+ preparation = 'automatic',
78
+ progressive,
70
79
  rowSizing = 'content',
71
80
  rows,
72
81
  skeletonRows,
@@ -95,6 +104,62 @@ const columnDensity = getTableColumnDensity(columns);
95
104
  const resolvedTone = tone ?? dataTone;
96
105
  const hasSortableColumns = columns.some((column) => column.sort);
97
106
  const hasFooter = Astro.slots.has('footer');
107
+ const configuredProgressive =
108
+ progressive && typeof progressive === 'object' ? progressive : undefined;
109
+ const batchSize = Math.max(
110
+ 1,
111
+ Math.floor(configuredProgressive?.batchSize ?? tableProgressiveBatchSize),
112
+ );
113
+ const resolvedRows = rows.map((row, index): TableRow => ({
114
+ ...row,
115
+ key: row.key ?? `${paginationKey ?? 'table'}-${index + 1}`,
116
+ }));
117
+ const inferredProgressive =
118
+ progressive === undefined && resolvedRows.length > batchSize
119
+ ? {
120
+ batchSize,
121
+ entries: resolvedRows.map((row) => ({
122
+ key: row.key!,
123
+ filters: row.filters,
124
+ sortValues: row.sortValues,
125
+ })),
126
+ }
127
+ : undefined;
128
+ const progressiveModel =
129
+ progressive === false
130
+ ? undefined
131
+ : configuredProgressive ?? inferredProgressive;
132
+ const hasProgressiveRows = Boolean(
133
+ progressiveModel && progressiveModel.entries.length > batchSize,
134
+ );
135
+ const hasProgressivePagination = hasProgressiveRows || clientProgressive;
136
+ const initialSkeletonRows = Math.max(
137
+ 1,
138
+ Math.floor(
139
+ skeletonRows ??
140
+ Math.min(
141
+ batchSize,
142
+ (progressiveModel?.entries.length ?? resolvedRows.length) || 1,
143
+ ),
144
+ ),
145
+ );
146
+ const preparesRows =
147
+ (resolvedRows.length > 0 || clientProgressive) && !errorMessage;
148
+ const initialLoadingState = errorMessage
149
+ ? 'error'
150
+ : resolvedRows.length === 0 && !clientProgressive
151
+ ? 'empty'
152
+ : 'pending';
153
+ const progressiveManifest = hasProgressiveRows
154
+ ? JSON.stringify({
155
+ ...progressiveModel,
156
+ batchSize,
157
+ }).replaceAll('<', '\\u003c')
158
+ : undefined;
159
+ const rowKeys = new Set(resolvedRows.map((row) => row.key));
160
+ const progressiveKeys = new Set(
161
+ progressiveModel?.entries.map((entry) => entry.key) ?? [],
162
+ );
98
163
 
99
164
  if (header === 'visible' && hasSortableColumns && !sortMessages) {
100
165
  throw new Error('Sortable table columns require localized sort messages');
@@ -102,6 +167,21 @@ if (header === 'visible' && hasSortableColumns && !sortMessages) {
102
167
  if (pagination?.nextHref && !paginationMessages) {
103
168
  throw new Error('Paginated tables require localized pagination messages');
104
169
  }
170
+ if (clientProgressive && !paginationMessages) {
171
+ throw new Error('Client-progressive tables require localized messages');
172
+ }
173
+ if (progressiveModel?.sources && !paginationKey) {
174
+ throw new Error('External progressive tables require a stable pagination key');
175
+ }
176
+ if (rowKeys.size !== resolvedRows.length) {
177
+ throw new Error('Table rows require unique keys');
178
+ }
179
+ if (
180
+ progressiveModel &&
181
+ progressiveKeys.size !== progressiveModel.entries.length
182
+ ) {
183
+ throw new Error('Progressive table entries require unique keys');
184
+ }
105
185
  ---
106
186
 
107
187
  <div
@@ -110,18 +190,32 @@ if (pagination?.nextHref && !paginationMessages) {
110
190
  data-widget="table"
111
191
  data-composition="table"
112
192
  data-table-column-density={columnDensity}
193
+ data-table-client-progressive={clientProgressive ? true : undefined}
113
194
  data-table-density={density}
114
195
  data-table-inline-overflow={inlineOverflow}
115
196
  data-table-index-minimum-rem={
116
197
  minimumInlineSize ? Number.parseFloat(minimumInlineSize) : undefined
117
198
  }
118
199
  data-table-layout={layout}
200
+ data-table-loading-state={initialLoadingState}
201
+ data-table-preparation={preparation}
119
202
  data-table-key={paginationKey}
120
203
  data-table-scroll-root
121
204
  data-table-sticky-header={stickyHeader ? true : undefined}
122
205
  data-tone={resolvedTone}
123
206
  style={tableStyle}
124
207
  >
208
+ {
209
+ preparesRows ? (
210
+ <script is:inline>
211
+ document.documentElement.setAttribute('data-nox-table-enhanced', '');
212
+ document.currentScript?.parentElement?.setAttribute(
213
+ 'aria-busy',
214
+ 'true',
215
+ );
216
+ </script>
217
+ ) : null
218
+ }
125
219
  <SurfaceGroup>
126
220
  <slot name="heading" />
127
221
  {
@@ -137,17 +231,17 @@ if (pagination?.nextHref && !paginationMessages) {
137
231
  filterEmptyMessage={filterEmptyMessage}
138
232
  layout={layout}
139
233
  rowSizing={rowSizing}
140
- rows={rows}
234
+ rows={resolvedRows}
235
+ skeletonRows={preparesRows ? initialSkeletonRows : 0}
141
236
  />
142
237
  {
143
- pagination && paginationMessages ? (
238
+ hasProgressivePagination || (pagination && paginationMessages) ? (
144
239
  <TablePagination
145
- columns={columns}
146
- errorLabel={paginationMessages.error}
147
- loadingLabel={paginationMessages.loading}
148
- nextLabel={paginationMessages.nextPage}
240
+ errorLabel={paginationMessages?.error ?? ''}
241
+ loadingLabel={paginationMessages?.loading ?? ''}
242
+ nextLabel={paginationMessages?.nextPage ?? ''}
149
243
  pagination={pagination}
150
- skeletonRows={skeletonRows}
244
+ progressive={hasProgressivePagination}
151
245
  />
152
246
  ) : null
153
247
  }
@@ -159,6 +253,27 @@ if (pagination?.nextHref && !paginationMessages) {
159
253
  ) : null
160
254
  }
161
255
  </SurfaceGroup>
256
+ {
257
+ progressiveManifest ? (
258
+ <script
259
+ is:inline
260
+ type="application/json"
261
+ data-table-progressive-manifest
262
+ set:html={progressiveManifest}
263
+ />
264
+ ) : null
265
+ }
266
+ {
267
+ preparesRows ? (
268
+ <template data-table-skeleton-template>
269
+ <table>
270
+ <tbody>
271
+ <TableSkeleton columns={columns} rows={initialSkeletonRows} />
272
+ </tbody>
273
+ </table>
274
+ </template>
275
+ ) : null
276
+ }
162
277
  </div>
163
278
 
164
279
  <script>
@@ -5,6 +5,7 @@ import type { Tone } from '../../theme/contracts';
5
5
  import TableSortIcon from '../../components/icons/TableSortIcon.astro';
6
6
  import TextBlock from '../../primitives/text/TextBlock/TextBlock.astro';
7
7
  import TableCellContent from './TableCell.astro';
8
+ import TableSkeleton from './TableSkeleton.astro';
8
9
  import {
9
10
  getTableColumnAlignment,
10
11
  type TableLayout,
@@ -20,6 +21,7 @@ type Props = HTMLAttributes<'div'> & {
20
21
  layout?: TableLayout;
21
22
  rowSizing?: 'content' | 'fill';
22
23
  rows: readonly TableRow[];
24
+ skeletonRows?: number;
23
25
  };
24
26
 
25
27
  const {
@@ -33,6 +35,7 @@ const {
33
35
  layout = 'default',
34
36
  rowSizing = 'content',
35
37
  rows,
38
+ skeletonRows = 0,
36
39
  ...attrs
37
40
  }: Props = Astro.props;
38
41
 
@@ -86,6 +89,11 @@ const stateMessage =
86
89
  ))
87
90
  }
88
91
  </tr>
92
+ {
93
+ skeletonRows > 0 ? (
94
+ <TableSkeleton columns={columns} rows={skeletonRows} />
95
+ ) : null
96
+ }
89
97
  {
90
98
  state && stateMessage ? (
91
99
  <tr class="table-body__row" data-table-state={state}>
@@ -41,6 +41,12 @@
41
41
  min-inline-size: max-content;
42
42
  }
43
43
 
44
+ .table-cell[data-table-cell-kind='actions'] {
45
+ inline-size: max-content;
46
+ min-inline-size: max-content;
47
+ overflow: visible;
48
+ }
49
+
44
50
  .table-cell[data-table-cell-kind='tags']:not([data-wrap])
45
51
  > [data-composition='inline']
46
52
  > [data-widget='tag'] {
@@ -6,6 +6,7 @@ import TextPair from '../../components/data-display/TextPair/TextPair.astro';
6
6
  import Media from '../../primitives/media/Media/Media.astro';
7
7
  import Inline from '../../composition/Inline/Inline.astro';
8
8
  import Tag from '../../components/data-display/Tag/Tag.astro';
9
+ import Button from '../../components/actions/Button/Button.astro';
9
10
 
10
11
  type Props = {
11
12
  cell: TableCell;
@@ -84,3 +85,33 @@ const formatValue = (value: DisplayTextValue): string => String(value);
84
85
  </Inline>
85
86
  ) : null
86
87
  }
88
+
89
+ {
90
+ cell.kind === 'actions' ? (
91
+ <Inline align="center" gap="xs" justify="end" wrap={false}>
92
+ {cell.actions.map((action) => {
93
+ const Icon = action.icon;
94
+
95
+ return (
96
+ <Button
97
+ aria-controls={action.modalTargetId}
98
+ aria-haspopup={action.modalTargetId ? 'dialog' : undefined}
99
+ aria-label={action.label}
100
+ borderless={action.borderless !== false}
101
+ href={action.href}
102
+ hoverTone={action.hoverTone}
103
+ iconOnly
104
+ size="sm"
105
+ title={action.label}
106
+ tone={action.tone}
107
+ data-modal-open={action.modalTargetId}
108
+ >
109
+ <span data-tone={action.iconTone}>
110
+ <Icon />
111
+ </span>
112
+ </Button>
113
+ );
114
+ })}
115
+ </Inline>
116
+ ) : null
117
+ }
@@ -6,6 +6,7 @@ type TableFooterPadding = 'none' | 'sm' | 'md' | 'lg';
6
6
 
7
7
  type Props = HTMLAttributes<'div'> & {
8
8
  padding?: TableFooterPadding;
9
+ surface?: boolean;
9
10
  tone?: Tone;
10
11
  };
11
12
 
@@ -13,6 +14,7 @@ const {
13
14
  class: className,
14
15
  'data-tone': dataTone,
15
16
  padding = 'md',
17
+ surface = true,
16
18
  tone,
17
19
  ...attrs
18
20
  }: Props = Astro.props;
@@ -26,7 +28,7 @@ const resolvedTone = tone ?? dataTone;
26
28
  data-padding={padding}
27
29
  data-table-footer
28
30
  data-tone={resolvedTone}
29
- data-surface
31
+ data-surface={surface ? true : undefined}
30
32
  >
31
33
  <slot />
32
34
  </div>
@@ -53,6 +53,7 @@ const hasSortableColumns = columns.some((column) => column.sort);
53
53
  type="button"
54
54
  data-table-sort-column={column.id}
55
55
  data-table-sort-direction="none"
56
+ data-table-sort-first-direction={column.sort.firstDirection}
56
57
  data-table-sort-label={column.label}
57
58
  data-table-sort-type={column.sort.type}
58
59
  data-table-sort-tie-breaker={column.sort.tieBreaker}