@stacksjs/defaults 0.75.241 → 0.75.243
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/ai/skills/stacks-actions/SKILL.md +1 -1
- package/ai/skills/stacks-native/CATALOG.md +6 -6
- package/app/Actions/Dashboard/hq-read.ts +144 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/resources/components/Dashboard/Commerce/CommerceManufacturersTable.stx +58 -61
- package/resources/components/Dashboard/Commerce/CommerceUnitsTable.stx +59 -61
- package/resources/components/Dashboard/Commerce/ProductWaitlistTable.stx +49 -61
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistTable.stx +66 -71
- package/resources/components/Dashboard/Library/DependenciesDashboard.stx +68 -14
- package/resources/components/Dashboard/Realtime/RealtimeDashboard.stx +25 -26
- package/resources/components/Dashboard/UI/Table.stx +244 -9
- package/stx-components-plugin.ts +8 -1
|
@@ -12,6 +12,36 @@ import { formatCurrency } from '@stacksjs/commerce/money'
|
|
|
12
12
|
* meant a table whose parent ignored the event had sort arrows that did
|
|
13
13
|
* nothing. It now sorts what it was given. A parent that sorts server-side
|
|
14
14
|
* sets `manual-sort` and keeps the event.
|
|
15
|
+
*
|
|
16
|
+
* ## The slots, and what to use instead
|
|
17
|
+
*
|
|
18
|
+
* Three upstream stx limitations, and together they are why this component
|
|
19
|
+
* had no callers at all while 69 dashboard components hand-rolled a `<table>`:
|
|
20
|
+
*
|
|
21
|
+
* - `{{ … }}` in slot content is evaluated at slot-expansion time against an
|
|
22
|
+
* empty context and written out as an empty string, so the expression is
|
|
23
|
+
* gone before the client loop runs. Attribute bindings (`x-text`, `:attr`,
|
|
24
|
+
* `@click`) survive intact, so half of a slot works and the text silently
|
|
25
|
+
* vanishes. stacksjs/stx#2095.
|
|
26
|
+
* - `stx typecheck` does not bind scoped slot props, so every reference to
|
|
27
|
+
* `row` in `<template #cell="{ row }">` is a TS2304, in every binding form.
|
|
28
|
+
* It is a required CI step, so correct usage cannot land. stacksjs/stx#2096.
|
|
29
|
+
* - `$slots.name` evaluates falsy inside a compound condition, so every
|
|
30
|
+
* `$slots` reference below stands alone and the branches are split rather
|
|
31
|
+
* than combined. stacksjs/stx#2098.
|
|
32
|
+
*
|
|
33
|
+
* So prefer the props over the slots until those are fixed:
|
|
34
|
+
*
|
|
35
|
+
* - action buttons: `rowActions` + the `row-action` event, with `when` for
|
|
36
|
+
* per-row availability and `busy-key`/`busy-value` to disable a row that is
|
|
37
|
+
* being written
|
|
38
|
+
* - a second line under a value: the column's `subtitleKey`
|
|
39
|
+
* - a pill that only marks some rows: a `badge` column with `emptyText: ''`
|
|
40
|
+
* - page-level filters that belong beside the table: `#toolbar`, which sits
|
|
41
|
+
* outside the row loop and so is unaffected by #2095
|
|
42
|
+
*
|
|
43
|
+
* `Library/DependenciesDashboard` and the Commerce `*Table.stx` migrations are
|
|
44
|
+
* the worked examples. See stacksjs/stacks#2924 and stacksjs/stacks#2925.
|
|
15
45
|
*/
|
|
16
46
|
interface DashboardTableColumn {
|
|
17
47
|
key: string
|
|
@@ -29,7 +59,7 @@ interface DashboardTableColumn {
|
|
|
29
59
|
* formatter function because props cross an attribute boundary, where a
|
|
30
60
|
* function does not survive.
|
|
31
61
|
*/
|
|
32
|
-
type?: 'text' | 'number' | 'currency' | 'date' | 'boolean' | 'badge'
|
|
62
|
+
type?: 'text' | 'number' | 'currency' | 'date' | 'datetime' | 'boolean' | 'badge'
|
|
33
63
|
/**
|
|
34
64
|
* For a `badge` column: the classes each value wears. A value with no
|
|
35
65
|
* entry gets a neutral pill, so a new status never renders unstyled.
|
|
@@ -48,7 +78,20 @@ interface DashboardTableColumn {
|
|
|
48
78
|
*/
|
|
49
79
|
badgeLabels?: Record<string, string>
|
|
50
80
|
/**
|
|
51
|
-
*
|
|
81
|
+
* Row field rendered as a second, quieter line under the value.
|
|
82
|
+
*
|
|
83
|
+
* A name with its slug, abbreviation or description underneath is by far the
|
|
84
|
+
* most common custom cell in the dashboard - 38 of the 67 hand-rolled tables
|
|
85
|
+
* have one - and it is the only reason most of them needed a slot at all.
|
|
86
|
+
* Doing it here keeps them off the `cell` slot, which cannot carry text
|
|
87
|
+
* (stacksjs/stx#2095). A row with no value for the key renders one line.
|
|
88
|
+
*/
|
|
89
|
+
subtitleKey?: string
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* What an empty cell reads as. Defaults to `-`. On a `badge` column, `''`
|
|
93
|
+
* means no pill at all for rows without a value, which is what a flag
|
|
94
|
+
* column wants.
|
|
52
95
|
*
|
|
53
96
|
* Per column because the right word is specific: a shipping table wants
|
|
54
97
|
* "No minimum" for an absent free-shipping threshold, which is a statement
|
|
@@ -70,6 +113,50 @@ interface DashboardTableColumn {
|
|
|
70
113
|
class?: string
|
|
71
114
|
/** Hidden until switched on in the column menu. */
|
|
72
115
|
hidden?: boolean
|
|
116
|
+
/**
|
|
117
|
+
* Route this column through the `cell` slot and leave every other column on
|
|
118
|
+
* the built-in rendering.
|
|
119
|
+
*
|
|
120
|
+
* Without this, a caller that needs custom markup in one column has to
|
|
121
|
+
* re-implement all of them, because the slot replaces the default content
|
|
122
|
+
* wholesale. Per-column slots would be the obvious answer and are not
|
|
123
|
+
* reachable: stx matches slot names literally, so `:name="'cell-' + key"`
|
|
124
|
+
* never resolves and its content is dropped silently.
|
|
125
|
+
*
|
|
126
|
+
* A table where no column sets this keeps the old behaviour and sends every
|
|
127
|
+
* cell through the slot, so existing callers are unaffected.
|
|
128
|
+
* See stacksjs/stacks#2924.
|
|
129
|
+
*/
|
|
130
|
+
slotted?: boolean
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* An icon button in the actions column, declared rather than slotted.
|
|
135
|
+
*
|
|
136
|
+
* The `actions` slot needs `row`, and `stx typecheck` rejects every reference
|
|
137
|
+
* to a scoped slot prop (stacksjs/stx#2096), so a table with action buttons
|
|
138
|
+
* could not be migrated at all. Props and events cross the attribute boundary
|
|
139
|
+
* cleanly, which this does: Table renders the buttons and emits `row-action`
|
|
140
|
+
* with the action name and the row.
|
|
141
|
+
*/
|
|
142
|
+
interface DashboardTableRowAction {
|
|
143
|
+
/** Identifies this action in the `row-action` payload. */
|
|
144
|
+
action: string
|
|
145
|
+
/** Read out as "<label> <row[actionLabelKey]>", e.g. "Edit Kilogram". */
|
|
146
|
+
label: string
|
|
147
|
+
/** Iconify class, e.g. `i-hugeicons-edit-01`. */
|
|
148
|
+
icon: string
|
|
149
|
+
/** Colour class for the icon. Defaults to the usual blue. */
|
|
150
|
+
class?: string
|
|
151
|
+
/**
|
|
152
|
+
* Show this action only on rows where `row[key]` equals `equals`.
|
|
153
|
+
*
|
|
154
|
+
* Approving, seating or cancelling is usually only offered on the rows it
|
|
155
|
+
* applies to - a restaurant party can be seated while it is still waiting,
|
|
156
|
+
* and not afterwards. Without this the whole actions column had to be a
|
|
157
|
+
* slot, which is the thing that could not be migrated.
|
|
158
|
+
*/
|
|
159
|
+
when?: { key: string, equals: unknown }
|
|
73
160
|
}
|
|
74
161
|
|
|
75
162
|
type DashboardTableRow = Record<string, any>
|
|
@@ -100,6 +187,17 @@ const columnToggle = useReactiveProp('columnToggle', false)
|
|
|
100
187
|
const exportable = useReactiveProp('exportable', false)
|
|
101
188
|
const exportFilename = useReactiveProp('exportFilename', 'table')
|
|
102
189
|
const stickyFirstColumn = useReactiveProp('stickyFirstColumn', false)
|
|
190
|
+
const rowActions = useReactiveProp<DashboardTableRowAction[]>('rowActions', [])
|
|
191
|
+
const actionLabelKey = useReactiveProp('actionLabelKey', '')
|
|
192
|
+
/**
|
|
193
|
+
* Disable every action on the row currently being written.
|
|
194
|
+
*
|
|
195
|
+
* `busyValue` is whatever the parent is tracking - usually the id of the row
|
|
196
|
+
* whose request is in flight - and `busyKey` is the field it compares against,
|
|
197
|
+
* defaulting to the row key. Empty means nothing is busy.
|
|
198
|
+
*/
|
|
199
|
+
const busyKey = useReactiveProp('busyKey', '')
|
|
200
|
+
const busyValue = useReactiveProp<unknown>('busyValue', '')
|
|
103
201
|
|
|
104
202
|
const selectedKeys = state<TableKey[]>([])
|
|
105
203
|
const sortKey = state('')
|
|
@@ -167,9 +265,18 @@ function displayValue(row: DashboardTableRow, column: DashboardTableColumn): str
|
|
|
167
265
|
return Number.isFinite(amount) ? amount.toLocaleString() : String(value)
|
|
168
266
|
}
|
|
169
267
|
if (column.type === 'date') {
|
|
170
|
-
const at =
|
|
268
|
+
const at = toDate(value)
|
|
171
269
|
return Number.isNaN(at.getTime()) ? String(value) : at.toLocaleDateString()
|
|
172
270
|
}
|
|
271
|
+
// An event or log row is ordered by when it happened, and to the minute, so
|
|
272
|
+
// the date alone is not enough to read it. Separate from `date` rather than
|
|
273
|
+
// a flag on it because the two render differently everywhere they appear.
|
|
274
|
+
if (column.type === 'datetime') {
|
|
275
|
+
const at = toDate(value)
|
|
276
|
+
return Number.isNaN(at.getTime())
|
|
277
|
+
? String(value)
|
|
278
|
+
: at.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' })
|
|
279
|
+
}
|
|
173
280
|
if (column.type === 'boolean')
|
|
174
281
|
return value ? 'Yes' : 'No'
|
|
175
282
|
|
|
@@ -217,6 +324,31 @@ function badgeClass(row: DashboardTableRow, column: DashboardTableColumn): strin
|
|
|
217
324
|
return `${BADGE_BASE} ${column.badgeClasses?.[key] ?? BADGE_NEUTRAL}`
|
|
218
325
|
}
|
|
219
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Whether this row gets a pill at all.
|
|
329
|
+
*
|
|
330
|
+
* A badge column with `emptyText: ''` is a flag: it marks the rows that have
|
|
331
|
+
* the value and leaves the rest blank, rather than drawing an empty pill
|
|
332
|
+
* around a dash on every other row.
|
|
333
|
+
*/
|
|
334
|
+
function hasBadge(row: DashboardTableRow, column: DashboardTableColumn): boolean {
|
|
335
|
+
return badgeLabel(row, column) !== ''
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Parse a stored timestamp.
|
|
340
|
+
*
|
|
341
|
+
* SQLite hands back `2026-01-01 12:00:00`, with a space and no zone. `new
|
|
342
|
+
* Date()` reads that as local time where it reads it at all, so every row
|
|
343
|
+
* shifted by the viewer's offset - a date near midnight landing on the wrong
|
|
344
|
+
* day. The hand-rolled tables each normalised it themselves; this is the same
|
|
345
|
+
* knowledge in the one place that needs it.
|
|
346
|
+
*/
|
|
347
|
+
function toDate(value: unknown): Date {
|
|
348
|
+
const text = String(value)
|
|
349
|
+
return new Date(/^\d{4}-\d{2}-\d{2} \d/.test(text) ? `${text.replace(' ', 'T')}Z` : text)
|
|
350
|
+
}
|
|
351
|
+
|
|
220
352
|
/** The pill's text: the mapped label, else the value with its underscores read as spaces. */
|
|
221
353
|
function badgeLabel(row: DashboardTableRow, column: DashboardTableColumn): string {
|
|
222
354
|
const value = rawValue(row, column.key)
|
|
@@ -244,8 +376,11 @@ function compare(a: any, b: any, type: DashboardTableColumn['type']): number {
|
|
|
244
376
|
|
|
245
377
|
if (type === 'number' || type === 'currency')
|
|
246
378
|
return Number(a) - Number(b)
|
|
247
|
-
|
|
248
|
-
|
|
379
|
+
// `datetime` sorts as a date too. Left out, it fell through to the string
|
|
380
|
+
// comparison below, which is only accidentally right for ISO timestamps and
|
|
381
|
+
// wrong for every other format.
|
|
382
|
+
if (type === 'date' || type === 'datetime')
|
|
383
|
+
return toDate(a).getTime() - toDate(b).getTime()
|
|
249
384
|
if (type === 'boolean')
|
|
250
385
|
return (a ? 1 : 0) - (b ? 1 : 0)
|
|
251
386
|
|
|
@@ -456,6 +591,55 @@ function rowClass(index: number): string {
|
|
|
456
591
|
].filter(Boolean).join(' ')
|
|
457
592
|
}
|
|
458
593
|
|
|
594
|
+
/**
|
|
595
|
+
* Whether any column opted into the `cell` slot.
|
|
596
|
+
*
|
|
597
|
+
* Once one does, only those columns route through the slot and the rest keep
|
|
598
|
+
* the built-in rendering. A table that marks none of them behaves as it always
|
|
599
|
+
* did: the slot, when given, takes every cell. `$slots` is template-only, so
|
|
600
|
+
* the presence of the slot is checked alongside this in the markup.
|
|
601
|
+
*/
|
|
602
|
+
function hasSlottedColumns(): boolean {
|
|
603
|
+
return columns().some(column => column.slotted)
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Whether the actions column is shown at all. */
|
|
607
|
+
function hasRowActions(): boolean {
|
|
608
|
+
return rowActions().length > 0
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** The quieter second line, when the column asked for one and the row has it. */
|
|
612
|
+
function subtitleValue(row: DashboardTableRow, column: DashboardTableColumn): string {
|
|
613
|
+
if (!column.subtitleKey)
|
|
614
|
+
return ''
|
|
615
|
+
const value = rawValue(row, column.subtitleKey)
|
|
616
|
+
return value === null || value === undefined ? '' : String(value)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** "Edit Kilogram", built from the action's label and the row's own name. */
|
|
620
|
+
function rowActionLabel(row: DashboardTableRow, action: DashboardTableRowAction): string {
|
|
621
|
+
const key = actionLabelKey() || rowKey()
|
|
622
|
+
const name = rawValue(row, key)
|
|
623
|
+
return name === null || name === undefined || name === '' ? action.label : `${action.label} ${String(name)}`
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** Whether this action applies to this row. */
|
|
627
|
+
function showsRowAction(row: DashboardTableRow, action: DashboardTableRowAction): boolean {
|
|
628
|
+
return !action.when || rawValue(row, action.when.key) === action.when.equals
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** Whether this row has a write in flight, so its actions are inert. */
|
|
632
|
+
function rowIsBusy(row: DashboardTableRow): boolean {
|
|
633
|
+
const value = busyValue()
|
|
634
|
+
if (value === '' || value === null || value === undefined)
|
|
635
|
+
return false
|
|
636
|
+
return rawValue(row, busyKey() || rowKey()) === value
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function rowActionIconClass(action: DashboardTableRowAction): string {
|
|
640
|
+
return `block h-4 w-4 ${action.class || 'text-blue-500'} ${action.icon}`
|
|
641
|
+
}
|
|
642
|
+
|
|
459
643
|
function cellClass(column: DashboardTableColumn, index: number): string {
|
|
460
644
|
return [
|
|
461
645
|
index === 0 && !selectable() ? 'pl-6' : 'pl-4',
|
|
@@ -476,8 +660,13 @@ function cellClass(column: DashboardTableColumn, index: number): string {
|
|
|
476
660
|
</div>
|
|
477
661
|
|
|
478
662
|
<!-- Toolbar: only rendered when something asked for it. -->
|
|
663
|
+
{{-- `$slots.toolbar` used to be the last arm of this `||`, which only ever
|
|
664
|
+
worked because one of the calls before it was usually true: `$slots`
|
|
665
|
+
does not resolve inside a compound expression, so a table whose ONLY
|
|
666
|
+
toolbar content came from the slot rendered no toolbar at all. The
|
|
667
|
+
slot-only case gets its own branch below. See stacksjs/stx#2098. --}}
|
|
479
668
|
<div
|
|
480
|
-
:if="searchable() || columnToggle() || exportable()
|
|
669
|
+
:if="searchable() || columnToggle() || exportable()"
|
|
481
670
|
class="flex flex-wrap gap-3 items-center justify-between px-4 py-3 border-b border-stone-200/80 dark:border-neutral-700"
|
|
482
671
|
>
|
|
483
672
|
<div class="flex flex-wrap gap-2 items-center">
|
|
@@ -552,6 +741,17 @@ function cellClass(column: DashboardTableColumn, index: number): string {
|
|
|
552
741
|
</div>
|
|
553
742
|
</div>
|
|
554
743
|
|
|
744
|
+
{{-- A table that only contributes toolbar content through the slot, with
|
|
745
|
+
none of the built-in controls switched on. --}}
|
|
746
|
+
<div
|
|
747
|
+
:else-if="$slots.toolbar"
|
|
748
|
+
class="flex flex-wrap gap-3 items-center justify-between px-4 py-3 border-b border-stone-200/80 dark:border-neutral-700"
|
|
749
|
+
>
|
|
750
|
+
<div class="flex flex-wrap gap-2 items-center">
|
|
751
|
+
<slot name="toolbar" />
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
|
|
555
755
|
<div class="overflow-x-auto">
|
|
556
756
|
<table class="w-full">
|
|
557
757
|
<thead :class="'bg-stone-50/80 dark:bg-neutral-800/50 ' + (stickyHeader() ? 'sticky top-0 z-20' : '')">
|
|
@@ -591,7 +791,12 @@ function cellClass(column: DashboardTableColumn, index: number): string {
|
|
|
591
791
|
</th>
|
|
592
792
|
</template>
|
|
593
793
|
|
|
794
|
+
{{-- `$slots` only resolves as a standalone reference: inside a
|
|
795
|
+
compound expression like `$slots.actions || hasRowActions()` the
|
|
796
|
+
whole condition comes out falsy, so the column silently vanished.
|
|
797
|
+
Hence the pair. See stacksjs/stx#2098. --}}
|
|
594
798
|
<th :if="$slots.actions" scope="col" class="px-4 py-3 w-12"><span class="sr-only">Actions</span></th>
|
|
799
|
+
<th :else-if="hasRowActions()" scope="col" class="px-4 py-3 w-12"><span class="sr-only">Actions</span></th>
|
|
595
800
|
</tr>
|
|
596
801
|
|
|
597
802
|
<!-- Per-column filter row. -->
|
|
@@ -623,6 +828,7 @@ function cellClass(column: DashboardTableColumn, index: number): string {
|
|
|
623
828
|
</td>
|
|
624
829
|
</template>
|
|
625
830
|
<td :if="$slots.actions" class="px-4 py-2"></td>
|
|
831
|
+
<td :else-if="hasRowActions()" class="px-4 py-2"></td>
|
|
626
832
|
</tr>
|
|
627
833
|
</thead>
|
|
628
834
|
|
|
@@ -641,16 +847,45 @@ function cellClass(column: DashboardTableColumn, index: number): string {
|
|
|
641
847
|
|
|
642
848
|
<template :for="(column, columnIndex) in visibleColumns()">
|
|
643
849
|
<td :class="cellClass(column, columnIndex)">
|
|
644
|
-
|
|
645
|
-
|
|
850
|
+
{{-- No `$slots` here: it only resolves standalone, so the
|
|
851
|
+
compound condition this used to carry made every slotted
|
|
852
|
+
cell render nothing. The slot's own fallback covers the
|
|
853
|
+
"caller gave no slot" case instead. See stacksjs/stx#2098. --}}
|
|
854
|
+
<template :if="column.slotted || !hasSlottedColumns()">
|
|
855
|
+
<slot name="cell" :row="row" :column="column" :value="row[column.key]">
|
|
856
|
+
<span :if="column.type === 'badge' && hasBadge(row, column)" :class="badgeClass(row, column)">{{ badgeLabel(row, column) }}</span>
|
|
857
|
+
<span :if="column.type !== 'badge'">{{ displayValue(row, column) }}</span>
|
|
858
|
+
<p :if="subtitleValue(row, column)" class="mt-1 text-neutral-500 text-xs dark:text-neutral-400">{{ subtitleValue(row, column) }}</p>
|
|
859
|
+
</slot>
|
|
860
|
+
</template>
|
|
861
|
+
<template :else>
|
|
862
|
+
<span :if="column.type === 'badge' && hasBadge(row, column)" :class="badgeClass(row, column)">{{ badgeLabel(row, column) }}</span>
|
|
646
863
|
<span :if="column.type !== 'badge'">{{ displayValue(row, column) }}</span>
|
|
647
|
-
|
|
864
|
+
<p :if="subtitleValue(row, column)" class="mt-1 text-neutral-500 text-xs dark:text-neutral-400">{{ subtitleValue(row, column) }}</p>
|
|
865
|
+
</template>
|
|
648
866
|
</td>
|
|
649
867
|
</template>
|
|
650
868
|
|
|
651
869
|
<td :if="$slots.actions" class="px-4 w-12" :class="compact() ? 'py-2' : 'py-3'">
|
|
652
870
|
<slot name="actions" :row="row" />
|
|
653
871
|
</td>
|
|
872
|
+
<td :else-if="hasRowActions()" class="px-4 w-12" :class="compact() ? 'py-2' : 'py-3'">
|
|
873
|
+
<div class="flex gap-1 justify-end">
|
|
874
|
+
<template :for="action in rowActions()">
|
|
875
|
+
<Button
|
|
876
|
+
:if="showsRowAction(row, action)"
|
|
877
|
+
variant="ghost"
|
|
878
|
+
size="sm"
|
|
879
|
+
iconOnly
|
|
880
|
+
:disabled="rowIsBusy(row)"
|
|
881
|
+
:ariaLabel="rowActionLabel(row, action)"
|
|
882
|
+
@click="emit('row-action', { action: action.action, row })"
|
|
883
|
+
>
|
|
884
|
+
<span aria-hidden="true" :class="rowActionIconClass(action)"></span>
|
|
885
|
+
</Button>
|
|
886
|
+
</template>
|
|
887
|
+
</div>
|
|
888
|
+
</td>
|
|
654
889
|
</tr>
|
|
655
890
|
</template>
|
|
656
891
|
</tbody>
|
package/stx-components-plugin.ts
CHANGED
|
@@ -58,9 +58,16 @@ export default {
|
|
|
58
58
|
* defines `<Button />`, and cannot quietly replace the framework's.
|
|
59
59
|
*/
|
|
60
60
|
components: [
|
|
61
|
-
library,
|
|
62
61
|
// Relative to this file, which is what stx resolves plugin entries against.
|
|
63
62
|
'resources/components',
|
|
63
|
+
// `library` is a package root too, and it used to sit above the line
|
|
64
|
+
// before this one - so for 16 names present in both trees the package
|
|
65
|
+
// answered, which is the exact thing the paragraph above says cannot
|
|
66
|
+
// happen. `<EmptyState>` was the visible one: the framework's takes an
|
|
67
|
+
// Iconify class, the package's takes raw SVG and prints it through
|
|
68
|
+
// `{!! icon !!}`, so ten dashboard components rendered the class name as
|
|
69
|
+
// text inside a grey circle. See stacksjs/stacks#2927.
|
|
70
|
+
library,
|
|
64
71
|
...packageDirs(),
|
|
65
72
|
],
|
|
66
73
|
}
|