Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a new quote component #2727

Merged
merged 2 commits into from
Mar 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/components/src/components/component-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { KolModal } from './modal/component';
import { KolNav } from './nav/component';
import { KolPagination } from './pagination/component';
import { KolProcess } from './progress/component';
import { KolQuote } from './quote/component';
import { KolSelect } from './select/component';
import { KolSkipNav } from './skip-nav/component';
import { KolSpanWc } from './span/component';
Expand Down Expand Up @@ -98,6 +99,7 @@ export const COMPONENTS = [
KolNav,
KolPagination,
KolProcess,
KolQuote,
KolSelect,
KolSkipNav,
KolSpan,
Expand Down
101 changes: 101 additions & 0 deletions packages/components/src/components/quote/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { h, Component, Host, JSX, Prop, State, Watch } from '@stencil/core';
import { watchString, watchValidator } from '../../utils/prop.validators';
import { ComponentApi, KoliBriQuoteVariant, States } from './types';

@Component({
tag: 'kol-quote',
styleUrls: {
default: './style.css',
},
shadow: true,
})
export class KolQuote implements ComponentApi {
/**
* The caption of the quote.
*/
@Prop() public _caption?: string;

/**
* The href is a URL that designates a source document or message for the information quoted.
*/
@Prop() public _href!: string;

/**
* The text of the quote.
*/
@Prop() public _quote!: string;

/**
* The variant of the quote.
*/
@Prop() public _variant?: KoliBriQuoteVariant = 'inline';

@State() public state: States = {
_href: '…', // ⚠ required
_quote: '…', // ⚠ required
_variant: 'inline',
};

@Watch('_caption')
public validateCaption(value?: string): void {
watchString(this, '_caption', value);
}

@Watch('_href')
public validateHref(value?: string): void {
watchString(this, '_href', value);
}

@Watch('_quote')
public validateQuote(value?: string): void {
watchString(this, '_quote', value);
}

@Watch('_variant')
public validateVariant(value?: KoliBriQuoteVariant): void {
watchValidator(this, '_variant', (value) => value === 'block' || value === 'inline', new Set(['block', 'inline']), value);
}

public componentWillLoad(): void {
this.validateCaption(this._caption);
this.validateHref(this._href);
this.validateQuote(this._quote);
this.validateVariant(this._variant);
}

public render(): JSX.Element {
const hideExpertSlot = this.state._quote !== '';
return (
<Host>
<figure
class={{
[this.state._variant]: true,
}}
>
{this.state._variant === 'block' ? (
<blockquote cite={this.state._href}>
{this.state._quote}
<span aria-hidden={hideExpertSlot ? 'true' : undefined} hidden={hideExpertSlot}>
<slot name="expert" />
</span>
</blockquote>
) : (
<q cite={this.state._href}>
{this.state._quote}
<span aria-hidden={hideExpertSlot ? 'true' : undefined} hidden={hideExpertSlot}>
<slot name="expert" />
</span>
</q>
)}
{typeof this.state._caption === 'string' && this.state._caption.length > 0 && (
<figcaption>
<cite>
<kol-link _href={this.state._href} _label={this.state._caption} _target="_blank" />
</cite>
</figcaption>
)}
</figure>
</Host>
);
}
}
13 changes: 13 additions & 0 deletions packages/components/src/components/quote/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Quote

The quote component is implemented in two variants. The first variant is the default `short` variant as inline quote with quotation marks. The second variant is the indented `long` variant. The indented variant is used to highlight a text passage or information visually.

Both variants can be extended with a `cite` element. The `cite` element is used to identify the source of a quotation and will be displayed below the quote as a link.

## References

- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/quote
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite
- https://www.mediaevent.de/html/quote.html
- https://www.mediaevent.de/html/cite.html
- https://accessibleweb.com/question-answer/what-is-a-block-quote-and-when-should-i-use-it/
28 changes: 28 additions & 0 deletions packages/components/src/components/quote/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@import '../style.css';

cite,
figure,
q + figcaption {
display: inline;
margin: 0;
padding: 0;
}

quote::before {
content: '»';
padding-right: 0.5em;
}
quote::after {
content: '«';
padding-left: 0.5em;
}

cite::before {
content: '—';
}
.block cite::before {
padding-right: 0.5em;
}
.inline cite::before {
padding: 0.5em;
}
46 changes: 46 additions & 0 deletions packages/components/src/components/quote/test/html.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { mixMembers } from 'stencil-awesome-test';
import { getLinkHtml } from '../../link/test/html.mock';
import { Props, States } from '../types';

type Slot = {
expert?: string;
};

export const getQuoteHtml = (props: Props, slots: Slot = {}): string => {
const state = mixMembers<Props, States>(
{
_href: '…', // ⚠ required
_quote: '…', // ⚠ required
_variant: 'inline',
},
props
);
const showExpertSlot = state._quote === '';
return `<kol-quote>
<mock:shadow-root>
<figure>
<${state._variant === 'block' ? 'blockquote' : 'q'} cite="${state._href}">
${state._quote}
<span${showExpertSlot && typeof slots.expert === 'string' ? `` : ` aria-hidden="true" hidden=""`}>
<slot name="expert">
${showExpertSlot && typeof slots.expert === 'string' ? slots.expert : ``}
</slot>
</span>
</${state._variant === 'block' ? 'blockquote' : 'q'}>
${
typeof state._caption === 'string' && state._caption.length > 0
? `<figcaption>
<cite>
${getLinkHtml({
_href: state._href,
_label: state._caption,
_target: '_blank',
})}
</cite>
</figcaption>`
: ``
}
</figure>
</mock:shadow-root>
</kol-quote>`;
};
25 changes: 25 additions & 0 deletions packages/components/src/components/quote/test/snapshot.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { h } from '@stencil/core';
import { newSpecPage, SpecPage } from '@stencil/core/testing';

import { COMPONENTS } from '../../component-list';
import { executeTests } from 'stencil-awesome-test';
import { Props } from '../types';
import { getQuoteHtml } from './html.mock';

executeTests<Props>(
'Quote',
async (props): Promise<SpecPage> => {
const page = await newSpecPage({
components: COMPONENTS,
template: () => <kol-quote {...props} />,
});
return page;
},
{
_caption: ['Caption'],
_href: ['https://www.example.com'],
_quote: ['Text of the Quote'],
_variant: ['block', 'inline'],
},
getQuoteHtml
);
28 changes: 28 additions & 0 deletions packages/components/src/components/quote/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Generic } from '@a11y-ui/core';

export type KoliBriQuoteVariant = 'block' | 'inline';

/**
* API for the Quote component.
*/
type RequiredProps = {
href: string; // URL to the source of the quote (cite)
quote: string;
};
type OptionalProps = {
caption: string;
variant: KoliBriQuoteVariant;
};
export type Props = Generic.Element.Members<RequiredProps, OptionalProps>;

type RequiredStates = {
href: string; // URL to the source of the quote (cite)
quote: string;
variant: KoliBriQuoteVariant;
};
type OptionalStates = {
caption: string;
};
export type States = Generic.Element.Members<RequiredStates, OptionalStates>;

export type ComponentApi = Generic.Element.ComponentApi<RequiredProps, OptionalProps, RequiredStates, OptionalStates>;
50 changes: 50 additions & 0 deletions packages/components/src/index.bak.html
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,56 @@
</div>
</div>
</li>
<li>
<div class="row">
<div class="col-12">
<kol-heading _headline="" _level="2">Blockquote & Quote</kol-heading>
</div>
<div class="gap-2 col-12">
<section>
<kol-heading _headline="" _level="3">Long bockquote</kol-heading>
<kol-quote
_caption="RFC 1149"
_cite="https://datatracker.ietf.org/doc/html/rfc1149"
_quote="Avian carriers can provide high delay, low throughput, and low altitude
service. The connection topology is limited to a single point-to-point path
for each carrier, used with standard carriers, but many carriers can be used
without significant interference with each other, outside early spring.
This is because of the 3D ether space available to the carriers, in contrast
to the 1D ether used by IEEE802.3. The carriers have an intrinsic collision
avoidance system, which increases availability."
_variant="long"
>
</kol-quote>
<kol-quote _caption="RFC 1149" _cite="https://datatracker.ietf.org/doc/html/rfc1149" _quote="" _variant="long">
<span slot="expert"
>Avian carriers can provide high delay, low throughput, and low altitude service. The connection topology is limited to a single
point-to-point path for each carrier, used with standard carriers, but many carriers can be used without significant interference with each
other, outside early spring. This is because of the 3D ether space available to the carriers, in contrast to the 1D ether used by IEEE802.3.
The carriers have an intrinsic collision avoidance system, which increases availability.
</span>
</kol-quote>
</section>
<section>
<kol-heading _headline="" _level="3">Short quote</kol-heading>
<p>
According to Mozilla's website,
<kol-quote
_caption="RFC 1149"
_cite="https://www.mozilla.org/en-US/about/history/details/"
_quote="Firefox 1.0 was released in 2004 and became a big success."
/>
</p>
<p>
According to Mozilla's website,
<kol-quote _caption="RFC 1149" _cite="https://www.mozilla.org/en-US/about/history/details/" _quote="">
<span slot="expert">Firefox 1.0 was released in 2004 and became a big success.</span>
</kol-quote>
</p>
</section>
</div>
</div>
</li>
<li id="skip-nav">
<div class="row">
<div class="col-12">
Expand Down
50 changes: 50 additions & 0 deletions packages/components/src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,56 @@
</div>
</div>
</li>
<li>
<div class="row">
<div class="col-12">
<kol-heading _headline="" _level="2">Blockquote & Quote</kol-heading>
</div>
<div class="gap-2 col-12">
<section>
<kol-heading _headline="" _level="3">Long bockquote</kol-heading>
<kol-quote
_caption="RFC 1149"
_cite="https://datatracker.ietf.org/doc/html/rfc1149"
_quote="Avian carriers can provide high delay, low throughput, and low altitude
service. The connection topology is limited to a single point-to-point path
for each carrier, used with standard carriers, but many carriers can be used
without significant interference with each other, outside early spring.
This is because of the 3D ether space available to the carriers, in contrast
to the 1D ether used by IEEE802.3. The carriers have an intrinsic collision
avoidance system, which increases availability."
_variant="long"
>
</kol-quote>
<kol-quote _caption="RFC 1149" _cite="https://datatracker.ietf.org/doc/html/rfc1149" _quote="" _variant="long">
<span slot="expert"
>Avian carriers can provide high delay, low throughput, and low altitude service. The connection topology is limited to a single
point-to-point path for each carrier, used with standard carriers, but many carriers can be used without significant interference with each
other, outside early spring. This is because of the 3D ether space available to the carriers, in contrast to the 1D ether used by IEEE802.3.
The carriers have an intrinsic collision avoidance system, which increases availability.
</span>
</kol-quote>
</section>
<section>
<kol-heading _headline="" _level="3">Short quote</kol-heading>
<p>
According to Mozilla's website,
<kol-quote
_caption="RFC 1149"
_cite="https://www.mozilla.org/en-US/about/history/details/"
_quote="Firefox 1.0 was released in 2004 and became a big success."
/>
</p>
<p>
According to Mozilla's website,
<kol-quote _caption="RFC 1149" _cite="https://www.mozilla.org/en-US/about/history/details/" _quote="">
<span slot="expert">Firefox 1.0 was released in 2004 and became a big success.</span>
</kol-quote>
</p>
</section>
</div>
</div>
</li>
<li id="skip-nav">
<div class="row">
<div class="col-12">
Expand Down
1 change: 1 addition & 0 deletions packages/components/stencil.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const TAGS = [
'kol-nav',
'kol-pagination',
'kol-progress',
'kol-quote',
'kol-select',
'kol-skip-nav',
'kol-span',
Expand Down