Skip to content

Commit 65cb9f9

Browse files
committed
fix: wip knobs
1 parent 05e5dde commit 65cb9f9

File tree

8 files changed

+147
-55
lines changed

8 files changed

+147
-55
lines changed
Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
<pf-banner>Default Banner</pf-banner>
2-
<pf-banner variant="info">Blue Banner</pf-banner>
3-
<pf-banner variant="danger">Red Banner</pf-banner>
4-
<pf-banner variant="success">Green Banner</pf-banner>
5-
<pf-banner variant="warning">Gold Banner</pf-banner>
62

73
<script type="module">
84
import '@patternfly/elements/pf-banner/pf-banner.js';
95
</script>
10-
11-
<style>
12-
pf-banner + pf-banner {
13-
margin-block-start: 1rem;
14-
}
15-
</style>

elements/pf-button/pf-button.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export type ButtonVariant = (
2929
* actions a user can take in an application, like submitting a form, canceling a
3030
* process, or creating a new object. Buttons can also be used to take a user to a
3131
* new location, like another page inside of a web application, or an external site
32-
* such as help or documentation..
32+
* such as help or documentation.
33+
* @slot - Button text label
34+
* @slot icon - Button Icon, overrides `icon` attribute
3335
* @summary Allows users to perform an action when triggered
3436
* @cssprop {<length>} [--pf-c-button--FontSize=1rem]
3537
* @cssprop [--pf-c-button--FontWeight=400]

elements/pf-card/pf-card.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ export class PfCard extends LitElement {
9494
<div id="body"
9595
part="body"
9696
class="${classMap({ empty: this.#slots.isEmpty(null) })}">
97-
<slot></slot>
97+
<slot @slotchange=${() => this.requestUpdate()}></slot>
9898
</div>
9999
<footer id="footer"
100100
part="footer"

package.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,7 @@
9595
"build:core"
9696
],
9797
"files": [
98-
"elements/*/*.ts",
99-
"!elements/*/*.d.ts",
98+
"elements/*/*(!.d).ts",
10099
"elements/*/*.css",
101100
"elements/tsconfig.json",
102101
"tsconfig.settings.json"
@@ -193,13 +192,22 @@
193192
"command": "web-dev-server --open --watch",
194193
"service": true,
195194
"dependencies": [
195+
"build:tools",
196196
"build:elements",
197197
"analyze"
198198
]
199199
},
200200
"watch:core": {
201201
"service": true,
202-
"command": "tspc --build --watch ./core/pfe-core --pretty"
202+
"command": "tspc --build --watch ./core/pfe-core --pretty",
203+
"files": [
204+
"core/**/*.ts",
205+
"!core/**/*.d.ts",
206+
"core/tsconfig.json"
207+
],
208+
"output": [
209+
"*.tsbuildinfo"
210+
]
203211
},
204212
"watch:analyze": {
205213
"service": true,

tools/pfe-tools/dev-server/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export function pfeDevServerConfig(options?: PfeDevServerConfigOptions): DevServ
154154
'element-internals-polyfill': 'nodemodules',
155155
'lit-html': 'nodemodules',
156156
'lit': 'nodemodules',
157+
'zero-md': 'nodemodules',
157158
'@lit/reactive-element': 'nodemodules',
158159
'@lit/context': 'nodemodules',
159160
...config.importMapOptions?.providers,

tools/pfe-tools/dev-server/plugins/templates/index.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,30 @@
122122
</nav>
123123
</aside>
124124
<main>{% if templateContent %}
125-
<div data-demo="{{ demo.tagName }}">{{ templateContent | safe }}</div>{% else %}
125+
<div data-demo="{{ demo.tagName }}">
126+
{#- if it's the primary demo -#}
127+
{%- if demo.permalink.endsWith(demo.slug + '/demo/') -%}
128+
<script type="module">
129+
import '@patternfly/pfe-tools/elements/pft-element-knobs.ts';
130+
// TODO: discriminate
131+
import '@patternfly/elements/pf-select/pf-select.js';
132+
import '@patternfly/elements/pf-switch/pf-switch.js';
133+
import '@patternfly/elements/pf-text-input/pf-text-input.js';
134+
import '@patternfly/elements/pf-text-area/pf-text-area.js';
135+
</script>
136+
<pft-element-knobs tag="{{ demo.tagName }}">
137+
<template>
138+
{{- templateContent | noModulesOrStyles | safe -}}
139+
</template>
140+
{{- templateContent | noElement(demo.tagName) | safe -}}
141+
<script type="application/json" data-package="{{ manifest.packageJson.name }}">
142+
{{ manifest.manifest | dump(2) | safe }}
143+
</script>
144+
</pft-element-knobs>
145+
{%- else -%}
146+
{{- templateContent | safe -}}
147+
{%- endif -%}
148+
</div>{% else %}
126149
<section id="components">
127150
{% for primary, group in groupeddemos %}
128151
{% set first = group['0'] %}

tools/pfe-tools/dev-server/plugins/templates/knobs.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// TODO: discriminate
55
import '@patternfly/elements/pf-select/pf-select.js';
66
import '@patternfly/elements/pf-text-input/pf-text-input.js';
7+
import '@patternfly/elements/pf-text-area/pf-text-area.js';
78
</script>
89

910
<pft-element-knobs tag="{{ tagName }}">

tools/pfe-tools/elements/pft-element-knobs.ts

Lines changed: 106 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,22 @@ import type {
33
CustomElementDeclaration,
44
Declaration,
55
Package,
6+
Slot,
67
} from 'custom-elements-manifest';
78

89
import { LitElement, css, html, type PropertyValues } from 'lit';
910
import { customElement } from 'lit/decorators/custom-element.js';
1011
import { property } from 'lit/decorators/property.js';
1112
import { ifDefined } from 'lit/directives/if-defined.js';
1213

13-
type KnobRenderer<T> = (
14-
this: PftElementKnobs<HTMLElement>,
15-
member: T,
16-
info: T extends Attribute ? AttributeKnobInfo : KnobInfo,
17-
) => unknown;
14+
import 'zero-md';
1815

19-
interface KnobInfo {
16+
interface KnobInfo<E> {
17+
element: E;
2018
knobId: string;
2119
}
2220

23-
interface AttributeKnobInfo extends KnobInfo {
21+
interface AttributeKnobInfo<E> extends KnobInfo<E> {
2422
isBoolean: boolean;
2523
isEnum: boolean;
2624
isNullable: boolean;
@@ -29,7 +27,25 @@ interface AttributeKnobInfo extends KnobInfo {
2927
values: string[];
3028
}
3129

32-
export type AttributeRenderer = KnobRenderer<Attribute>;
30+
type ContentKnobInfo<E> = KnobInfo<E>;
31+
32+
type KnobRenderer<T, E extends HTMLElement = HTMLElement> = (
33+
this: PftElementKnobs<E>,
34+
member: T,
35+
info:
36+
T extends Attribute ? AttributeKnobInfo<E>
37+
: T extends Slot[] ? ContentKnobInfo<E>
38+
: KnobInfo<E>,
39+
) => unknown;
40+
41+
export type AttributeRenderer<E extends HTMLElement> = KnobRenderer<Attribute, E>;
42+
export type ContentRenderer<E extends HTMLElement> = KnobRenderer<Slot[], E>;
43+
44+
const isCheckable = (el: HTMLElement): el is HTMLElement & { checked: boolean } =>
45+
'checked' in el;
46+
47+
const isValue = (el: HTMLElement): el is HTMLElement & { value: string } =>
48+
'value' in el;
3349

3450
const isCustomElementDecl = (decl: Declaration): decl is CustomElementDeclaration =>
3551
'customElement' in decl;
@@ -41,11 +57,18 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
4157
#element {
4258
padding: 1em;
4359
}
44-
fieldset {
60+
61+
dl#slot-descriptions {
4562
display: grid;
46-
gap: 4px;
47-
grid-template-columns: max-content 1fr;
48-
align-items: center;
63+
gap: 8px;
64+
grid-template-columns: max-content auto;
65+
& dd {
66+
margin-inline-start: 0;
67+
}
68+
}
69+
70+
pf-text-area {
71+
width: 100%;
4972
}
5073
`,
5174
];
@@ -56,7 +79,9 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
5679

5780
@property({ attribute: false }) element: T | null = null;
5881

59-
@property({ attribute: false }) renderAttribute: AttributeRenderer = this.#renderAttribute;
82+
@property({ attribute: false }) renderAttribute: AttributeRenderer<T> = this.#renderAttribute;
83+
84+
@property({ attribute: false }) renderContent: ContentRenderer<T> = this.#renderContent;
6085

6186
#mo = new MutationObserver(this.#loadTemplate);
6287

@@ -101,7 +126,7 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
101126
}
102127
}
103128

104-
#getAttributeInfo(attribute: Attribute): AttributeKnobInfo {
129+
#getInfoForAttribute(attribute: Attribute, element: T): AttributeKnobInfo<T> {
105130
// NOTE: we assume typescript types
106131
const type = attribute?.type?.text ?? '';
107132
const isUnion = !!type.includes?.('|');
@@ -115,6 +140,7 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
115140
const knobId = `knob-attribute-${attribute.name}`;
116141
return {
117142
knobId,
143+
element,
118144
isBoolean,
119145
isEnum,
120146
isNullable,
@@ -124,55 +150,96 @@ export class PftElementKnobs<T extends HTMLElement> extends LitElement {
124150
};
125151
}
126152

127-
#renderAttribute(attribute: Attribute, info: AttributeKnobInfo) {
128-
const { knobId, isEnum, isBoolean, values } = info;
153+
#renderAttribute(attribute: Attribute, info: AttributeKnobInfo<T>) {
154+
const { knobId, element, isEnum, isBoolean, isNumber, values } = info;
129155
const QUOTE_RE = /^['"](.*)['"]$/;
156+
const attributeValue =
157+
element?.getAttribute(attribute.name)
158+
?? attribute.default?.replace(QUOTE_RE, '$1');
130159
return html`
131-
<label for="${knobId}">${attribute.name}</label>${isBoolean ? html`
132-
<input id="${knobId}"
133-
type="checkbox"
134-
?checked="${attribute.default === 'true'}"
135-
data-attribute="${attribute.name}">` : isEnum ? html`
160+
<h3><code>${attribute.name}</code></h3>
161+
<zero-md><script type="text/markdown">${attribute.summary ?? ''}</script></zero-md>
162+
<zero-md><script type="text/markdown">${attribute.description ?? ''}</script></zero-md>
163+
${isBoolean ? html`
164+
<label for="${knobId}">Present</label>
165+
<pf-switch id="${knobId}"
166+
?checked="${attribute.default === 'true'}"
167+
data-attribute="${attribute.name}"></pf-switch>` : isEnum ? html`
136168
<pf-select id="${knobId}"
137169
placeholder="Select a value"
170+
aria-label="Value"
138171
data-attribute="${attribute.name}"
139-
value="${ifDefined(attribute.default?.replace(QUOTE_RE, '$1'))}">${values!.map(x => html`
172+
value="${ifDefined(attributeValue)}">${values!.map(x => html`
140173
<pf-option>${x.trim().replace(QUOTE_RE, '$1')}</pf-option>`)}
141-
</pf-select>
142-
` : html`
174+
</pf-select>` : html`
143175
<pf-text-input id="${knobId}"
144-
value="${ifDefined(attribute.default?.replace(QUOTE_RE, '$1'))}"
176+
aria-label="Value"
177+
value="${ifDefined(attributeValue)}"
178+
type="${ifDefined(isNumber ? 'number' : undefined)}"
145179
helper-text="${ifDefined(attribute.type?.text)}"
146180
data-attribute="${attribute.name}"></pf-text-input>`}
147181
`;
148182
}
149183

184+
#renderContent(slots: Slot[], info: ContentKnobInfo<T>) {
185+
// todo : change listener is inflexible
186+
return html`
187+
<dl id="slot-descriptions">${slots.map(x => html`
188+
<dt>${x.name ? html`
189+
<code>${x.name}</code>` : html`
190+
<strong>Default slot</strong>`}
191+
</dt>
192+
<dd>
193+
<zero-md><script type="text/markdown">${x.summary ?? ''}</script></zero-md>
194+
<zero-md><script type="text/markdown">${x.description ?? ''}</script></zero-md>
195+
</dd>`)}
196+
</dl>
197+
<pf-text-area id="${info.knobId}"
198+
resize auto-resize
199+
aria-label="HTML Content"
200+
@input="${this.#onKnobChangedContent}"
201+
.value="${info.element.innerHTML}"></pf-text-area>
202+
`;
203+
}
204+
150205
#renderKnobs() {
151206
const decl = this.#elementDecl;
152207
const { element, tag, manifest } = this;
153208
if (element && decl && tag && manifest) {
154-
const { attributes } = decl;
155-
156-
const onChange = (e: Event & { target: HTMLInputElement }) => {
157-
if (e.target instanceof HTMLInputElement && e.target.type === 'checkbox') {
158-
this.element?.toggleAttribute(e.target.dataset.attribute!, e.target.checked);
159-
} else {
160-
this.element?.setAttribute(e.target.dataset.attribute!, e.target.value);
161-
}
162-
};
163-
209+
const { attributes, slots } = decl;
164210
return html`
165211
<form @submit="${(e: Event) => e.preventDefault()}">
166212
${!attributes ? '' : html`
167-
<fieldset @change="${onChange}" @input="${onChange}">
168-
<legend>Attributes</legend>
169-
${attributes.map(attr => this.renderAttribute(attr, this.#getAttributeInfo(attr)))}
170-
</fieldset>`}
213+
<section id="attributes"
214+
@change="${this.#onKnobChangeAttribute}"
215+
@input="${this.#onKnobChangeAttribute}">
216+
<h2>Attributes</h2>
217+
${attributes.map(x => this.renderAttribute(x, this.#getInfoForAttribute(x, element)))}
218+
</section>`}
219+
${!slots ? '' : html`
220+
<section>
221+
<h2>Slots</h2>
222+
${this.renderContent(slots, { knobId: 'knob-html-content', element })}
223+
</section>`}
171224
</form>
172225
`;
173226
}
174227
}
175228

229+
#onKnobChangeAttribute(e: Event & { target: HTMLElement }) {
230+
if (isCheckable(e.target)) {
231+
this.element?.toggleAttribute(e.target.dataset.attribute!, e.target.checked);
232+
} else if (isValue(e.target)) {
233+
this.element?.setAttribute(e.target.dataset.attribute!, e.target.value);
234+
}
235+
}
236+
237+
#onKnobChangedContent(e: Event & { target: HTMLInputElement }) {
238+
if (this.element) {
239+
this.element.innerHTML = e.target.value;
240+
}
241+
}
242+
176243
protected override render(): unknown {
177244
return html`
178245
<div id="element">${this.#node ?? ''}</div>

0 commit comments

Comments
 (0)