@usequeek/theme-kit 0.1.7 → 0.1.8

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/README.md CHANGED
@@ -54,6 +54,80 @@ Theme CSS ships with the package: `@usequeek/theme-kit/shared-blocks/core-blocks
54
54
  source. Anyone testing a theme with vitest needs the same `inline` line;
55
55
  anyone compiling one needs `moduleResolution: bundler` + `jsx: react-jsx`.
56
56
 
57
+ ## Custom data: metafields & metaobjects
58
+
59
+ Product pages receive custom data already resolved — never fetch it:
60
+
61
+ ```tsx
62
+ import { ProductMetafields } from '@usequeek/theme-kit/components/product-metafields';
63
+ import type { ProductPageProps } from '@usequeek/theme-kit/types/theme';
64
+
65
+ export default function ProductPage({ product, metafieldDefinitions }: ProductPageProps) {
66
+ return <ProductMetafields product={product} definitions={metafieldDefinitions} />;
67
+ }
68
+ ```
69
+
70
+ - MUST: place `<ProductMetafields product definitions={metafieldDefinitions} />`
71
+ on the product page. `product.metafields` (`types/product.ts`) arrives as
72
+ presented values — a scalar, scalar list, `MetaobjectEntry`, or
73
+ `MetaobjectEntry[]`; labels come via `metafieldDefinitions` (unknown keys
74
+ fall back to a humanised key). The component renders nothing when there is
75
+ nothing visible to show.
76
+ - MAY: compose your own layout with the exported `EntryCard` and `renderValue`
77
+ from the same module instead of placing the section.
78
+ - Style only: the section emits `core-metafields*` classes (`core-metafields`,
79
+ `__field`, `__label`, `__value`, `__text`, `__link`, `__image`, `__pre`,
80
+ `__list`, `__list-item`, `__entry-card`, `__entry-title`, `__entry-field`,
81
+ `__entry-field-label`, `__entry-field-value`, `__entry-grid`);
82
+ `DefaultMetaobjectPage` emits `core-metaobject-page*`. No kit CSS ships for
83
+ these — style them in your theme.
84
+ - The `metaobjects` section block (`shared-blocks/metaobjects`,
85
+ framework-owned) renders a merchant-picked type's entries when the page
86
+ supplies its data; it emits `core-block-metaobjects` with `--grid`/`--list`
87
+ modifiers, `core-block-metaobjects__list` with `--grid`/`--list` variants,
88
+ plus `__title` and `__card-link`. Never re-implement it — style the classes.
89
+ - Optional entry pages: provide `pages.Metaobject: FC<MetaobjectPageProps>`
90
+ (`entry` plus `definition { type; name; fields: [{ key; name; type }] }`).
91
+ When absent, core `DefaultMetaobjectPage`
92
+ (`components/default-metaobject-page`) is used.
93
+ - Dynamic sources (`$source` refs) resolve server-side; block data always
94
+ reaches the theme as literals — nothing to handle.
95
+
96
+ ## Attribution: Powered by Queek
97
+
98
+ Every storefront is a billboard — every footer variant of every theme renders
99
+ the core-owned attribution link:
100
+
101
+ ```tsx
102
+ import { PoweredByQueek } from '@usequeek/theme-kit/components/powered-by-queek';
103
+
104
+ export default function Footer() {
105
+ return (
106
+ <footer>
107
+ {/* ... */}
108
+ <PoweredByQueek />
109
+ </footer>
110
+ );
111
+ }
112
+ ```
113
+
114
+ - MUST: place `<PoweredByQueek />` on every footer variant (`footers/*.tsx`,
115
+ or `footer.tsx` where a theme has no `footers/` dir). The placement and
116
+ spacing are the theme's — the component is the content, the footer owns
117
+ where it sits. `theme-check` rule `theme/footer-shows-powered-by` enforces
118
+ this, so a theme cannot ship without it.
119
+ - Style only: the link emits `core-powered-by` with a
120
+ `core-powered-by__brand` hook on the `Queek` wordmark; an optional
121
+ `className` passthrough lets a footer position it (e.g.
122
+ `<PoweredByQueek className="my-footer__powered" />`). No kit CSS ships for
123
+ it — style the classes in your theme.
124
+ - The href (`https://usequeek.com/business`), `target="_blank"` and
125
+ `rel="noopener noreferrer"` are the contract — never re-implement the
126
+ markup by hand.
127
+ - `hidden` (default false) takes the component's visibility from a prop as
128
+ the seam for a possible future plan perk. There is no merchant-facing
129
+ toggle for it in v1 — always render it visible.
130
+
57
131
  ## Boundary
58
132
 
59
133
  The kit must never import the storefront app, its themes, or `lib/storefront`.
package/client.ts CHANGED
@@ -13,6 +13,7 @@ export * from './shared-blocks';
13
13
  export * from './components/blog';
14
14
  export * from './components/image';
15
15
  export * from './components/product-metafields';
16
+ export * from './components/powered-by-queek';
16
17
  export * from './components/default-metaobject-page';
17
18
  export * from './components/product-media-frame';
18
19
  export * from './components/variant-picker';
@@ -0,0 +1,36 @@
1
+ import type { JSX } from 'react';
2
+
3
+ export interface PoweredByQueekProps {
4
+ /** Extra class so a footer can position the link — styling stays theme-owned. */
5
+ className?: string;
6
+ /**
7
+ * Reserved for a future plan perk (paid removal). Defaults to false and is
8
+ * always rendered in v1 — there is no merchant-facing toggle for it.
9
+ */
10
+ hidden?: boolean;
11
+ }
12
+
13
+ /**
14
+ * Core-owned storefront attribution — the `ProductMetafields` pattern: themes
15
+ * import-and-place it, never re-implement it.
16
+ *
17
+ * Required on every footer variant of every theme (enforced by `theme-check`
18
+ * rule `theme/footer-shows-powered-by` in the storefront repo). The link
19
+ * target (`https://usequeek.com/business`) and `rel` are the contract —
20
+ * do not change them. Markup carries `core-powered-by` classes so each theme
21
+ * styles it in its own `theme.css`; no kit CSS ships for it.
22
+ */
23
+ export function PoweredByQueek({ className, hidden = false }: PoweredByQueekProps): JSX.Element | null {
24
+ if (hidden) return null;
25
+
26
+ return (
27
+ <a
28
+ href="https://usequeek.com/business"
29
+ target="_blank"
30
+ rel="noopener noreferrer"
31
+ className={className ? `core-powered-by ${className}` : 'core-powered-by'}
32
+ >
33
+ Powered by <strong className="core-powered-by__brand">Queek</strong>
34
+ </a>
35
+ );
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usequeek/theme-kit",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "The framework a Queek storefront theme is built against.",
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { renderToStaticMarkup } from 'react-dom/server';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { PoweredByQueek } from '../components/powered-by-queek';
5
+
6
+ function render(props?: { className?: string; hidden?: boolean }): string {
7
+ return renderToStaticMarkup(React.createElement(PoweredByQueek, props));
8
+ }
9
+
10
+ describe('PoweredByQueek', () => {
11
+ it('links to the Queek business page with the attribution contract intact', () => {
12
+ const html = render();
13
+
14
+ expect(html).toContain('<a');
15
+ expect(html).toContain('href="https://usequeek.com/business"');
16
+ expect(html).toContain('target="_blank"');
17
+ expect(html).toContain('rel="noopener noreferrer"');
18
+ });
19
+
20
+ it('renders "Powered by Queek" with the brand hook for theme styling', () => {
21
+ const html = render();
22
+
23
+ expect(html).toContain('core-powered-by');
24
+ expect(html).toContain('Powered by');
25
+ expect(html).toContain('<strong class="core-powered-by__brand">Queek</strong>');
26
+ });
27
+
28
+ it('passes a footer positioning class through alongside the core class', () => {
29
+ const html = render({ className: 'qn-footer4__powered' });
30
+
31
+ expect(html).toContain('class="core-powered-by qn-footer4__powered"');
32
+ });
33
+
34
+ it('renders nothing when hidden', () => {
35
+ expect(render({ hidden: true })).toBe('');
36
+ });
37
+ });