Skip to content

Commit

Permalink
refactor(cc-zone)!: rework properties to avoid impossible states
Browse files Browse the repository at this point in the history
BREAKING CHANGE: the properties have changed
- `state`: new property containing the whole state
- `zone`: property has been deleted as it is now part of the state
  • Loading branch information
florian-sanders-cc committed Jun 12, 2024
1 parent da42a08 commit 9e9dceb
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 26 deletions.
35 changes: 27 additions & 8 deletions src/components/cc-zone/cc-zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ import { skeletonStyles } from '../../styles/skeleton.js';
const SKELETON_ZONE = {
name: 'name',
country: '????????????',
countryCode: null,
lon: 0,
lat: 0,
city: '??????????',
tags: ['????????', '????????????'],
};

const PRIVATE_ZONE = 'scope:private';

/**
* @typedef {import('./cc-zone.types.js').ZoneState} ZoneState
* @typedef {import('./cc-zone.types.js').ZoneStateLoaded} ZoneStateLoaded
* @typedef {import('./cc-zone.types.js').ZoneModeType} ZoneModeType
* @typedef {import('../common.types.js').Zone} Zone
*/
Expand All @@ -26,7 +31,6 @@ const PRIVATE_ZONE = 'scope:private';
*
* ## Details
*
* * When `zone` is nullish, a skeleton screen UI pattern is displayed (loading hint).
* * When a tag prefixed with `infra:` is used, the corresponding logo is displayed.
* * When the `scope:private` tag is used, the optional `displayName` of the zone will be used instead of the City + Country.
* * If the browser supports it, the `countryCode` will be used to display a translated version of the country's name.
Expand All @@ -47,7 +51,7 @@ export class CcZone extends LitElement {
static get properties () {
return {
mode: { type: String, reflect: true },
zone: { type: Object },
state: { type: Object },
};
}

Expand All @@ -57,12 +61,16 @@ export class CcZone extends LitElement {
/** @type {ZoneModeType} Sets the mode of the component. */
this.mode = 'default';

/** @type {Zone|null} Sets the different details of the zone. */
this.zone = null;
/** @type {ZoneState} Sets the state of the component. */
this.state = { type: 'loading' };
}

// This is a bit irregular to do this but we need to reuse this text logic in a <select>.
// Moving this to a separated module feels overkill right now.
/**
* @param {Zone} zone
* @returns {string}
*/
static getText (zone) {
const { title, subtitle, infra } = CcZone._getTextParts(zone);
const titleAndSubtitle = [title, subtitle].filter((a) => a != null).join(', ');
Expand All @@ -71,6 +79,11 @@ export class CcZone extends LitElement {
: titleAndSubtitle;
}

/**
* @param {Zone} zone
* @returns {{ title: string, subtitle?: string, infra?: string }}
* @private
*/
static _getTextParts (zone) {
if (zone.tags.includes(PRIVATE_ZONE) && zone.displayName != null) {
return { title: zone.displayName };
Expand All @@ -87,20 +100,25 @@ export class CcZone extends LitElement {

render () {

const skeleton = (this.zone == null);
const zone = skeleton ? SKELETON_ZONE : this.zone;
const skeleton = this.state.type === 'loading';
const zone = this.state.type === 'loaded' ? this.state : SKELETON_ZONE;
const { title, subtitle, infra } = CcZone._getTextParts(zone);

return html`
<cc-img class="flag" ?skeleton=${skeleton} src=${ifDefined(getFlagUrl(zone.countryCode))} a11y-name=${ifDefined(zone.countryCode)}></cc-img>
<cc-img
class="flag"
?skeleton=${skeleton}
src=${ifDefined(getFlagUrl(zone.countryCode))}
a11y-name=${ifDefined(zone.countryCode)}
></cc-img>
<div class="wrapper-details-logo">
<div class="wrapper-details">
<div class="details">
<span class="title ${classMap({ skeleton })}">${title}</span>
<span class="subtitle ${classMap({ skeleton })}">${subtitle}</span>
</div>
${infra != null ? html`
<cc-img class="infra-logo" src=${getInfraProviderLogoUrl(infra)} a11y-name=${infra}></cc-img>
<cc-img class="infra-logo ${classMap({ skeleton })}" src=${getInfraProviderLogoUrl(infra)} a11y-name=${infra}></cc-img>
` : ''}
</div>
<div class="tag-list">
Expand All @@ -113,6 +131,7 @@ export class CcZone extends LitElement {
/**
* @param {string} tag - the tag to render
* @param {boolean} skeleton - display as skeleton or not
* @private
*/
_renderTag (tag, skeleton) {

Expand Down
77 changes: 60 additions & 17 deletions src/components/cc-zone/cc-zone.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ const conf = {
component: 'cc-zone',
};

/**
* @typedef {import('./cc-zone.js').CcZone} CcZone
* @typedef {import('./cc-zone.types.js').ZoneStateLoaded} ZoneStateLoaded
* @typedef {import('./cc-zone.types.js').ZoneStateLoading} ZoneStateLoading
* @typedef {import('./cc-zone.types.js').ZoneModeType} ZoneModeType
*/

/** @type {ZoneStateLoaded} */
const zoneDefault = {
type: 'loaded',
name: 'par',
country: 'France',
countryCode: 'FR',
Expand All @@ -21,7 +30,9 @@ const zoneDefault = {
tags: ['region:eu', 'infra:clever-cloud'],
};

/** @type {ZoneStateLoaded} */
const zoneWithInfra = {
type: 'loaded',
name: 'war',
country: 'Poland',
countryCode: 'PL',
Expand All @@ -31,7 +42,9 @@ const zoneWithInfra = {
tags: ['region:eu', 'infra:ovh'],
};

/** @type {ZoneStateLoaded} */
const zonePrivate = {
type: 'loaded',
name: 'acme-corp',
displayName: 'ACME Corp',
country: 'Germany',
Expand All @@ -42,7 +55,9 @@ const zonePrivate = {
tags: ['region:eu', 'scope:private'],
};

/** @type {ZoneStateLoaded} */
const zoneWithoutTags = {
type: 'loaded',
name: 'nyc',
country: 'United States',
countryCode: 'US',
Expand All @@ -52,7 +67,9 @@ const zoneWithoutTags = {
tags: [],
};

/** @type {ZoneStateLoaded} */
const zoneWithManyTags = {
type: 'loaded',
name: 'war',
country: 'Poland',
countryCode: 'PL',
Expand All @@ -63,43 +80,69 @@ const zoneWithManyTags = {
};

export const defaultStory = makeStory(conf, {
items: [{ zone: zoneDefault }, { zone: zoneDefault, mode: 'small' }, { zone: zoneDefault, mode: 'small-infra' }],
/** @type {{ state: ZoneStateLoaded, mode?: ZoneModeType }[]} */
items: [
{ state: zoneDefault },
{ state: zoneDefault, mode: 'small' },
{ state: zoneDefault, mode: 'small-infra' },
],
});

export const skeleton = makeStory(conf, {
items: [{}, { mode: 'small' }, { mode: 'small-infra' }],
export const loading = makeStory(conf, {
/** @type {{ state: ZoneStateLoading, mode?: ZoneModeType }[]} */
items: [
{ state: { type: 'loading' } },
{ state: { type: 'loading' }, mode: 'small' },
{ state: { type: 'loading' }, mode: 'small-infra' },
],
});

// NOTE: We don't need an error state for now

export const dataLoadedWithInfra = makeStory(conf, {
items: [{ zone: zoneWithInfra }, { zone: zoneWithInfra, mode: 'small' }, {
zone: zoneWithInfra, mode: 'small-infra',
}],
/** @type {{ state: ZoneStateLoaded, mode?: ZoneModeType }[]} */
items: [
{ state: zoneWithInfra },
{ state: zoneWithInfra, mode: 'small' },
{ state: zoneWithInfra, mode: 'small-infra' },
],
});

export const dataLoadedWithPrivate = makeStory(conf, {
items: [{ zone: zonePrivate }, { zone: zonePrivate, mode: 'small' }, { zone: zonePrivate, mode: 'small-infra' }],
/** @type {{ state: ZoneStateLoaded, mode?: ZoneModeType }[]} */
items: [
{ state: zonePrivate },
{ state: zonePrivate, mode: 'small' },
{ state: zonePrivate, mode: 'small-infra' },
],
});

export const dataLoadedWithNoTags = makeStory(conf, {
items: [{ zone: zoneWithoutTags }, { zone: zoneWithoutTags, mode: 'small' }, {
zone: zoneWithoutTags, mode: 'small-infra',
}],
/** @type {{ state: ZoneStateLoaded, mode?: ZoneModeType }[]} */
items: [
{ state: zoneWithoutTags },
{ state: zoneWithoutTags, mode: 'small' },
{ state: zoneWithoutTags, mode: 'small-infra' },
],
});

export const dataLoadedWithManyTags = makeStory(conf, {
items: [{ zone: zoneWithManyTags }, { zone: zoneWithManyTags, mode: 'small' }, {
zone: zoneWithManyTags, mode: 'small-infra',
}],
/** @type {{ state: ZoneStateLoaded, mode?: ZoneModeType }[]} */
items: [
{ state: zoneWithManyTags },
{ state: zoneWithManyTags, mode: 'small' },
{ state: zoneWithManyTags, mode: 'small-infra' },
],
});

export const simulations = makeStory(conf, {
items: [{}, {}],
simulations: [
storyWait(2000, ([component, componentWithInfra]) => {
component.zone = zoneDefault;
componentWithInfra.zone = zoneWithInfra;
}),
storyWait(2000,
/** @param {CcZone[]} components */
([component, componentWithInfra]) => {
component.state = zoneDefault;
componentWithInfra.state = zoneWithInfra;
}),
],
});
14 changes: 13 additions & 1 deletion src/components/cc-zone/cc-zone.types.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
type ZoneModeType = "default" | "small" | "small-infra";
import { Zone } from '../common.types.js';

export type ZoneState = ZoneStateLoaded | ZoneStateLoading;

export interface ZoneStateLoaded extends Zone {
type: 'loaded';
}

export interface ZoneStateLoading {
type: 'loading'
}

export type ZoneModeType = 'default' | 'small' | 'small-infra';
3 changes: 3 additions & 0 deletions src/components/common.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,14 @@ interface Redirection {
}

interface Zone {
name: string;
countryCode: string; // ISO 3166-1 alpha-2 code of the country (2 letters): "FR", "CA", "US"...
city: string; // Name of the city in english: "Paris", "Montreal", "New York City"...
country: string; // Name of the country in english: "France", "Canada", "United States"...
displayName?: string; // Optional display name for private zones (instead of displaying city + country): "ACME (dedicated)"...
tags: string[]; // Array of strings for semantic tags: ["region:eu", "infra:clever-cloud"], ["scope:private"]...
lat: number; // Latitude
lon: number; // Longitude
}

type ToggleStateType = 'off' | 'open' | 'close';
Expand Down

0 comments on commit 9e9dceb

Please sign in to comment.