@uniweb/kit 0.8.3 → 0.9.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"fuse.js": "^7.0.0",
|
|
39
39
|
"shiki": "^3.0.0",
|
|
40
40
|
"tailwind-merge": "^2.6.0",
|
|
41
|
-
"@uniweb/core": "0.
|
|
41
|
+
"@uniweb/core": "0.7.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/hooks/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { useWebsite, default } from './useWebsite.js'
|
|
2
2
|
export { useFetched } from './useFetched.js'
|
|
3
3
|
export { useCacheEntry } from './useCacheEntry.js'
|
|
4
|
+
export { useEntityDetail } from './useEntityDetail.js'
|
|
5
|
+
export { useCollectionQueryable } from './useCollectionQueryable.js'
|
|
4
6
|
export { useRouting } from './useRouting.js'
|
|
5
7
|
export { useActiveRoute } from './useActiveRoute.js'
|
|
6
8
|
export { useVersion } from './useVersion.js'
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCollectionQueryable — read the queryable-surface metadata for a
|
|
3
|
+
* collection.
|
|
4
|
+
*
|
|
5
|
+
* Authors declare the queryable surface of an entity in `site.yml`:
|
|
6
|
+
*
|
|
7
|
+
* collections:
|
|
8
|
+
* members:
|
|
9
|
+
* path: collections/members
|
|
10
|
+
* queryable:
|
|
11
|
+
* department:
|
|
12
|
+
* type: enum
|
|
13
|
+
* label: Department
|
|
14
|
+
* options: [biology, physics, chemistry, geology]
|
|
15
|
+
* tenured:
|
|
16
|
+
* type: boolean
|
|
17
|
+
* label: Tenured
|
|
18
|
+
* start_year:
|
|
19
|
+
* type: range
|
|
20
|
+
* label: Start year
|
|
21
|
+
* min: 1800
|
|
22
|
+
* max: 2025
|
|
23
|
+
*
|
|
24
|
+
* Foundations consume this hook to render filter UIs and compose
|
|
25
|
+
* where-objects from user interactions. The kit doesn't ship UI
|
|
26
|
+
* components for the controls (different foundations have different
|
|
27
|
+
* vocabularies); foundations build their own controls against the
|
|
28
|
+
* metadata returned here.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* function MemberFilters({ onChange }) {
|
|
32
|
+
* const queryable = useCollectionQueryable('members')
|
|
33
|
+
* const [values, setValues] = useState({})
|
|
34
|
+
* if (!queryable) return null
|
|
35
|
+
* return (
|
|
36
|
+
* <div>
|
|
37
|
+
* {Object.entries(queryable).map(([field, def]) => (
|
|
38
|
+
* <FilterControl
|
|
39
|
+
* key={field}
|
|
40
|
+
* field={field}
|
|
41
|
+
* def={def}
|
|
42
|
+
* value={values[field]}
|
|
43
|
+
* onChange={(v) => {
|
|
44
|
+
* const next = { ...values, [field]: v }
|
|
45
|
+
* setValues(next)
|
|
46
|
+
* onChange(composeWhereObject(next))
|
|
47
|
+
* }}
|
|
48
|
+
* />
|
|
49
|
+
* ))}
|
|
50
|
+
* </div>
|
|
51
|
+
* )
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* Returns null when the collection has no `queryable:` declared.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} collectionName - Name of the collection to read queryable
|
|
57
|
+
* metadata for. Pass null/undefined to skip (returns null).
|
|
58
|
+
* @returns {Object|null} The queryable metadata object, or null.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { getUniweb } from '@uniweb/core'
|
|
62
|
+
|
|
63
|
+
export function useCollectionQueryable(collectionName) {
|
|
64
|
+
if (!collectionName) return null
|
|
65
|
+
const website = getUniweb()?.activeWebsite
|
|
66
|
+
if (!website) return null
|
|
67
|
+
const config = website.config?.collections?.[collectionName]
|
|
68
|
+
if (!config || typeof config !== 'object') return null
|
|
69
|
+
const queryable = config.queryable
|
|
70
|
+
if (!queryable || typeof queryable !== 'object') return null
|
|
71
|
+
return queryable
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default useCollectionQueryable
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useEntityDetail — fetch the full record for a deferred-field collection.
|
|
3
|
+
*
|
|
4
|
+
* When a collection declares `deferred: [...]` in `site.yml`, the cascade
|
|
5
|
+
* payload omits the deferred fields. The full record (with deferred
|
|
6
|
+
* fields included) lives in a per-record file at
|
|
7
|
+
* `/data/<collection>/<slug>.json`. On dynamic-route pages, the framework
|
|
8
|
+
* routes the singular detail there automatically. Anywhere else (a card
|
|
9
|
+
* grid that wants to show a hover-card preview, a modal that opens an
|
|
10
|
+
* article body) — use this hook.
|
|
11
|
+
*
|
|
12
|
+
* Returns `{ data, error, loading }` like `useFetched`. Shares the same
|
|
13
|
+
* cache. Pass null/undefined to skip without subscribing.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* function ArticleCard({ article }) {
|
|
17
|
+
* const [open, setOpen] = useState(false)
|
|
18
|
+
* const { data: full, loading } = useEntityDetail(open ? article : null, {
|
|
19
|
+
* collection: 'articles',
|
|
20
|
+
* })
|
|
21
|
+
* return (
|
|
22
|
+
* <div>
|
|
23
|
+
* <h3>{article.title}</h3>
|
|
24
|
+
* <p>{article.excerpt}</p>
|
|
25
|
+
* <button onClick={() => setOpen(true)}>Read more</button>
|
|
26
|
+
* {open && (loading ? <Spinner /> : <ArticleBody html={full.body} />)}
|
|
27
|
+
* </div>
|
|
28
|
+
* )
|
|
29
|
+
* }
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { useFetched } from './useFetched.js'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {Object|null} record - A record from a cascade-delivered collection.
|
|
36
|
+
* Must have a `slug` field. Pass null/undefined to skip the fetch.
|
|
37
|
+
* @param {Object} [options]
|
|
38
|
+
* @param {string} options.collection - The collection name (e.g., 'articles').
|
|
39
|
+
* Required when record is non-null. Used to build the per-record file URL
|
|
40
|
+
* `/data/<collection>/<slug>.json`.
|
|
41
|
+
* @returns {{ data: any, error: string|null, loading: boolean }}
|
|
42
|
+
*/
|
|
43
|
+
export function useEntityDetail(record, options = {}) {
|
|
44
|
+
const collection = options?.collection
|
|
45
|
+
const request = (record && record.slug && collection)
|
|
46
|
+
? { path: `/data/${collection}/${record.slug}.json`, schema: collection }
|
|
47
|
+
: null
|
|
48
|
+
return useFetched(request)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default useEntityDetail
|