diff --git a/src/components.d.ts b/src/components.d.ts index ffb708432..f3ef87a3d 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -25,12 +25,14 @@ export namespace Components { interface ManifoldActivePlan { 'hideProvisionButton'?: boolean; 'isExistingResource'?: boolean; + 'linkFormat'?: string; 'plans': Catalog.ExpandedPlan[]; 'product'?: Catalog.ExpandedProduct; } interface ManifoldActivePlanAttributes extends StencilHTMLAttributes { 'hideProvisionButton'?: boolean; 'isExistingResource'?: boolean; + 'linkFormat'?: string; 'plans'?: Catalog.ExpandedPlan[]; 'product'?: Catalog.ExpandedProduct; } @@ -181,24 +183,26 @@ export namespace Components { } interface ManifoldLinkButton { - 'href': string; + 'href'?: string; + 'onClick'?: (e: Event) => void; 'rel'?: string; 'target'?: string; } interface ManifoldLinkButtonAttributes extends StencilHTMLAttributes { 'href'?: string; + 'onClick'?: (e: Event) => void; 'rel'?: string; 'target'?: string; } interface ManifoldMarketplaceResults { 'featured'?: string; - 'serviceLink'?: string; + 'linkFormat'?: string; 'services': Catalog.Product[]; } interface ManifoldMarketplaceResultsAttributes extends StencilHTMLAttributes { 'featured'?: string; - 'serviceLink'?: string; + 'linkFormat'?: string; 'services'?: Catalog.Product[]; } @@ -212,9 +216,9 @@ export namespace Components { */ 'featured'?: string; /** - * _(optional)_ If cards are `` tags, how should link work? + * _(optional)_ Link format structure, with `:product` placeholder */ - 'serviceLink'?: string; + 'linkFormat'?: string; } interface ManifoldMarketplaceAttributes extends StencilHTMLAttributes { /** @@ -226,9 +230,9 @@ export namespace Components { */ 'featured'?: string; /** - * _(optional)_ If cards are `` tags, how should link work? + * _(optional)_ Link format structure, with `:product` placeholder */ - 'serviceLink'?: string; + 'linkFormat'?: string; } interface ManifoldNumberInput { @@ -275,12 +279,15 @@ export namespace Components { interface ManifoldPlanDetails { 'hideProvisionButton': boolean; 'isExistingResource'?: boolean; + 'linkFormat'?: string; 'plan'?: Catalog.ExpandedPlan; 'product'?: Catalog.Product; } interface ManifoldPlanDetailsAttributes extends StencilHTMLAttributes { 'hideProvisionButton'?: boolean; 'isExistingResource'?: boolean; + 'linkFormat'?: string; + 'onManifold-planCTA-click'?: (event: CustomEvent) => void; 'onManifold-planUpdated'?: (event: CustomEvent) => void; 'plan'?: Catalog.ExpandedPlan; 'product'?: Catalog.Product; @@ -303,10 +310,14 @@ export namespace Components { */ 'connection': Connection; /** - * _(optional)_ Hide bottom-right button? + * _(optional)_ Hide button? */ 'hideProvisionButton'?: boolean; /** + * _(optional)_ Link format structure, with `:product`, `:plan`, and `:features` placeholders + */ + 'linkFormat'?: string; + /** * URL-friendly slug (e.g. `"jawsdb-mysql"`) */ 'productLabel': string; @@ -321,10 +332,14 @@ export namespace Components { */ 'connection'?: Connection; /** - * _(optional)_ Hide bottom-right button? + * _(optional)_ Hide button? */ 'hideProvisionButton'?: boolean; /** + * _(optional)_ Link format structure, with `:product`, `:plan`, and `:features` placeholders + */ + 'linkFormat'?: string; + /** * URL-friendly slug (e.g. `"jawsdb-mysql"`) */ 'productLabel'?: string; @@ -342,10 +357,13 @@ export namespace Components { } interface ManifoldProductPage { + 'linkFormat'?: string; 'product'?: Catalog.ExpandedProduct; 'provider'?: Catalog.Provider; } interface ManifoldProductPageAttributes extends StencilHTMLAttributes { + 'linkFormat'?: string; + 'onManifold-productCTA-click'?: (event: CustomEvent) => void; 'product'?: Catalog.ExpandedProduct; 'provider'?: Catalog.Provider; } @@ -356,6 +374,10 @@ export namespace Components { */ 'connection': Connection; /** + * _(optional)_ Link format structure, with `:product` placeholder + */ + 'linkFormat'?: string; + /** * URL-friendly slug (e.g. `"jawsdb-mysql"`) */ 'productLabel': string; @@ -366,6 +388,10 @@ export namespace Components { */ 'connection'?: Connection; /** + * _(optional)_ Link format structure, with `:product` placeholder + */ + 'linkFormat'?: string; + /** * URL-friendly slug (e.g. `"jawsdb-mysql"`) */ 'productLabel'?: string; @@ -391,10 +417,10 @@ export namespace Components { 'isCustom'?: boolean; 'isFeatured'?: boolean; 'label'?: string; + 'linkFormat'?: string; 'logo'?: string; 'name'?: string; 'productId'?: string; - 'serviceLink'?: string; } interface ManifoldServiceCardAttributes extends StencilHTMLAttributes { 'connection'?: Connection; @@ -402,11 +428,11 @@ export namespace Components { 'isCustom'?: boolean; 'isFeatured'?: boolean; 'label'?: string; + 'linkFormat'?: string; 'logo'?: string; 'name'?: string; 'onManifold-serviceCard-click'?: (event: CustomEvent) => void; 'productId'?: string; - 'serviceLink'?: string; } interface ManifoldServiceCategory { @@ -429,12 +455,12 @@ export namespace Components { interface ManifoldServicesTunnel { 'featured'?: string; - 'serviceLink'?: string; + 'linkFormat'?: string; 'services': Catalog.Product[]; } interface ManifoldServicesTunnelAttributes extends StencilHTMLAttributes { 'featured'?: string; - 'serviceLink'?: string; + 'linkFormat'?: string; 'services'?: Catalog.Product[]; } diff --git a/src/components/manifold-active-plan/manifold-active-plan.tsx b/src/components/manifold-active-plan/manifold-active-plan.tsx index 84f7f8d53..d9a6de652 100644 --- a/src/components/manifold-active-plan/manifold-active-plan.tsx +++ b/src/components/manifold-active-plan/manifold-active-plan.tsx @@ -7,6 +7,7 @@ import { Component, State, Prop } from '@stencil/core'; }) export class ManifoldActivePlan { @Prop() isExistingResource?: boolean; + @Prop() linkFormat?: string; @Prop() product?: Catalog.ExpandedProduct; @Prop() plans: Catalog.ExpandedPlan[] = []; @Prop() hideProvisionButton?: boolean; @@ -31,10 +32,11 @@ export class ManifoldActivePlan { />,
plan.id === this.selectedPlanId)} - hideProvisionButton={this.hideProvisionButton} + product={this.product} />
, ]; diff --git a/src/components/manifold-active-plan/readme.md b/src/components/manifold-active-plan/readme.md index dae45aec7..ed2936094 100644 --- a/src/components/manifold-active-plan/readme.md +++ b/src/components/manifold-active-plan/readme.md @@ -11,6 +11,7 @@ Hello | --------------------- | ----------------------- | ----------- | ------------------------------ | ----------- | | `hideProvisionButton` | `hide-provision-button` | | `boolean \| undefined` | `undefined` | | `isExistingResource` | `is-existing-resource` | | `boolean \| undefined` | `undefined` | +| `linkFormat` | `link-format` | | `string \| undefined` | `undefined` | | `plans` | -- | | `ExpandedPlan[]` | `[]` | | `product` | -- | | `ExpandedProduct \| undefined` | `undefined` | diff --git a/src/components/manifold-link-button/link-button.css b/src/components/manifold-link-button/link-button.css index 468227ea8..a14bb567a 100644 --- a/src/components/manifold-link-button/link-button.css +++ b/src/components/manifold-link-button/link-button.css @@ -1,14 +1,11 @@ a { - --padding-left-alt: 1em; - display: inline-flex; justify-content: center; align-items: center; box-sizing: border-box; - min-height: 2.5rem; - padding-top: 0.75em; + width: 100%; + height: 2.375rem; padding-right: 1.5em; - padding-bottom: 0.75em; padding-left: 1.5em; color: var(--mf-c-white); font-size: 15px; @@ -21,7 +18,7 @@ a { border-radius: var(--mf-radius-m); box-shadow: var(--mf-shadow-default); cursor: pointer; - transition: background-color 200ms, border-color 150ms, color 200ms; + transition: filter 200ms linear; &:focus, &:hover { diff --git a/src/components/manifold-link-button/manifold-link-button.tsx b/src/components/manifold-link-button/manifold-link-button.tsx index db6f8f35d..df7e13aea 100644 --- a/src/components/manifold-link-button/manifold-link-button.tsx +++ b/src/components/manifold-link-button/manifold-link-button.tsx @@ -6,13 +6,14 @@ import { Component, Prop } from '@stencil/core'; shadow: true, }) export class LinkButton { - @Prop() href: string; + @Prop() href?: string; + @Prop() onClick?: (e: Event) => void; @Prop() rel?: string; @Prop() target?: string; render() { return ( -
+ ); diff --git a/src/components/manifold-link-button/readme.md b/src/components/manifold-link-button/readme.md index d011e9f7d..5d600833f 100644 --- a/src/components/manifold-link-button/readme.md +++ b/src/components/manifold-link-button/readme.md @@ -7,11 +7,12 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| -------- | --------- | ----------- | --------------------- | ----------- | -| `href` | `href` | | `string` | `undefined` | -| `rel` | `rel` | | `string \| undefined` | `undefined` | -| `target` | `target` | | `string \| undefined` | `undefined` | +| Property | Attribute | Description | Type | Default | +| --------- | --------- | ----------- | ----------------------------------- | ----------- | +| `href` | `href` | | `string \| undefined` | `undefined` | +| `onClick` | -- | | `((e: Event) => void) \| undefined` | `undefined` | +| `rel` | `rel` | | `string \| undefined` | `undefined` | +| `target` | `target` | | `string \| undefined` | `undefined` | ---------------------------------------------- diff --git a/src/components/manifold-marketplace-results/manifold-marketplace-results.tsx b/src/components/manifold-marketplace-results/manifold-marketplace-results.tsx index f22e4978a..bd764fe7f 100644 --- a/src/components/manifold-marketplace-results/manifold-marketplace-results.tsx +++ b/src/components/manifold-marketplace-results/manifold-marketplace-results.tsx @@ -3,13 +3,13 @@ import { Component, Prop } from '@stencil/core'; @Component({ tag: 'manifold-marketplace-results', styleUrl: 'marketplace-results.css' }) export class ManifoldMarketplace { @Prop() featured?: string; - @Prop() serviceLink?: string; + @Prop() linkFormat?: string; @Prop() services: Catalog.Product[] = []; private formatHref(label: string): string { if (typeof label !== 'string') return ''; - if (!this.serviceLink) return ''; - return this.serviceLink.replace(':service', label); + if (!this.linkFormat) return ''; + return this.linkFormat.replace(/:product/gi, label); } private isFeatured(label: string) { @@ -31,7 +31,7 @@ export class ManifoldMarketplace { logo={logo_url} name={name} productId={id} - serviceLink={this.formatHref(label)} + linkFormat={this.formatHref(label)} /> ))} diff --git a/src/components/manifold-marketplace-results/readme.md b/src/components/manifold-marketplace-results/readme.md index 3b3dad1ad..41eb66c9d 100644 --- a/src/components/manifold-marketplace-results/readme.md +++ b/src/components/manifold-marketplace-results/readme.md @@ -7,11 +7,11 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------- | -------------- | ----------- | --------------------- | ----------- | -| `featured` | `featured` | | `string \| undefined` | `undefined` | -| `serviceLink` | `service-link` | | `string \| undefined` | `undefined` | -| `services` | -- | | `Product[]` | `[]` | +| Property | Attribute | Description | Type | Default | +| ------------ | ------------- | ----------- | --------------------- | ----------- | +| `featured` | `featured` | | `string \| undefined` | `undefined` | +| `linkFormat` | `link-format` | | `string \| undefined` | `undefined` | +| `services` | -- | | `Product[]` | `[]` | ---------------------------------------------- diff --git a/src/components/manifold-marketplace/manifold-marketplace.tsx b/src/components/manifold-marketplace/manifold-marketplace.tsx index 75982195c..63d215a85 100644 --- a/src/components/manifold-marketplace/manifold-marketplace.tsx +++ b/src/components/manifold-marketplace/manifold-marketplace.tsx @@ -9,8 +9,8 @@ export class ManifoldMarketplace { @Element() el: HTMLElement; /** _(hidden)_ Passed by `` */ @Prop() connection: Connection = connections[Env.Prod]; - /** _(optional)_ If cards are `` tags, how should link work? */ - @Prop() serviceLink?: string; + /** _(optional)_ Link format structure, with `:product` placeholder */ + @Prop() linkFormat?: string; /** _(optional)_ Comma-separated list of featured products (labels) */ @Prop() featured?: string; @State() services: Catalog.Product[] = []; @@ -27,7 +27,7 @@ export class ManifoldMarketplace { return ( diff --git a/src/components/manifold-marketplace/readme.md b/src/components/manifold-marketplace/readme.md index 9d264fbe9..97f581eec 100644 --- a/src/components/manifold-marketplace/readme.md +++ b/src/components/manifold-marketplace/readme.md @@ -8,33 +8,25 @@ A list of all Manifold services. ## Navigation -There are two ways to trigger navigation when clicking on a service card. - -### 1. Provide a URL Format - -```html - -``` - -This turns the service cards into `` tags with the URL structure of ``, etc. `:service` is the only dynamic paramater accepted. Everything else in the formula will be displayed as-is. - -For each service, the URL slug can be found at `https://manifold.co/services/:service`. - -### 2. Use JavaScript Events - -If you omit the `service-link` property, clicking a service card will emit an event named `manifold-serviceCard-click`, which you can handle with a standard event listener: +When users click on a product card, you expect something to happen, right? By +default, service cards will emit a `manifold-serviceCard-click` custom event +whenever a user clicks anywhere on a card. You can listen for it like so, +and use this value to navigate client-side or perform some other action of +your choice: ```js -// Attach a listener to the manifold-marketplace element -var marketplace = document.querySelector('manifold-marketplace'); -marketplace.addEventListener('manifold-serviceCard-click', e => { - alert(`You just clicked ${e.detail.label}`); +document.addEventListener('manifold-serviceCard-click', { detail: { label } } => { + alert(`You clicked the card for ${label}`); }); +``` -// Or, attach the listener to the window object -window.addEventListener('manifold-serviceCard-click', e => { - alert(`You just clicked ${e.detail.label}`); -}); +Alternately, if you’d like the service cards to be plain, ol’ `` tags, you +can specify a `link-format` attribute, where `:product` will be substituted +with each product’s URL-friendly slug: + +```html + + ``` #### Handling Events in React @@ -43,8 +35,8 @@ Attaching listeners to custom components in React [requires the use of refs](htt ```js marketplaceLoaded(node) { - node.addEventListener("manifold-serviceCard-click", e => { - alert(`You just clicked ${e.detail.label}`); + node.addEventListener("manifold-serviceCard-click", ({ detail: { label } }) => { + alert(`You clicked the card for ${label}`); }); } @@ -58,11 +50,11 @@ render() { ## Properties -| Property | Attribute | Description | Type | Default | -| ------------- | -------------- | --------------------------------------------------------------- | --------------------- | ----------------------- | -| `connection` | -- | _(hidden)_ Passed by `` | `Connection` | `connections[Env.Prod]` | -| `featured` | `featured` | _(optional)_ Comma-separated list of featured products (labels) | `string \| undefined` | `undefined` | -| `serviceLink` | `service-link` | _(optional)_ If cards are `` tags, how should link work? | `string \| undefined` | `undefined` | +| Property | Attribute | Description | Type | Default | +| ------------ | ------------- | --------------------------------------------------------------- | --------------------- | ----------------------- | +| `connection` | -- | _(hidden)_ Passed by `` | `Connection` | `connections[Env.Prod]` | +| `featured` | `featured` | _(optional)_ Comma-separated list of featured products (labels) | `string \| undefined` | `undefined` | +| `linkFormat` | `link-format` | _(optional)_ Link format structure, with `:product` placeholder | `string \| undefined` | `undefined` | ---------------------------------------------- diff --git a/src/components/manifold-plan-details/manifold-plan-details.e2e.ts b/src/components/manifold-plan-details/manifold-plan-details.e2e.ts index edd3d14fa..3b33b9fcd 100644 --- a/src/components/manifold-plan-details/manifold-plan-details.e2e.ts +++ b/src/components/manifold-plan-details/manifold-plan-details.e2e.ts @@ -209,5 +209,31 @@ describe(``, () => { const button = await page.find('manifold-plan-details >>> manifold-link-button'); expect(button).toBeNull(); }); + + it('formats links correctly', async () => { + // Set properties and wait + const page = await newE2EPage({ + html: '', + }); + await page.$eval('manifold-plan-details', (elm: any) => { + elm.product = { body: { label: 'cloudcube' } }; + elm.plan = { + body: { + label: 'venture', + expanded_features: [ + { type: 'number', label: 'storage', value: { numeric_details: { min: 500 } } }, + { type: 'boolean', label: 'highAvailability', value: { label: 'true' } }, + ], + }, + }; + elm.linkFormat = '/create/:product/?plan=:plan&:features'; + }); + await page.waitForChanges(); + + const el = await page.find('manifold-plan-details >>> manifold-link-button'); + expect(await el.getProperty('href')).toBe( + '/create/cloudcube/?plan=venture&storage=500&highAvailability=true' + ); + }); }); }); diff --git a/src/components/manifold-plan-details/manifold-plan-details.tsx b/src/components/manifold-plan-details/manifold-plan-details.tsx index 1c15601ce..52ab74303 100644 --- a/src/components/manifold-plan-details/manifold-plan-details.tsx +++ b/src/components/manifold-plan-details/manifold-plan-details.tsx @@ -4,8 +4,6 @@ import { initialFeatures } from '../../utils/plan'; import { FeatureValue } from './components/FeatureValue'; import { FeatureLabel } from './components/FeatureLabel'; -const RESOURCE_CREATE = '/resource/create?product='; // TODO get actual url - @Component({ tag: 'manifold-plan-details', styleUrl: 'plan-details.css', @@ -14,6 +12,7 @@ const RESOURCE_CREATE = '/resource/create?product='; // TODO get actual url export class ManifoldPlanDetails { @Prop() isExistingResource?: boolean; @Prop() plan?: Catalog.ExpandedPlan; + @Prop() linkFormat?: string; @Prop() product?: Catalog.Product; @Prop() hideProvisionButton: boolean = false; @State() features: UserFeatures = {}; @@ -27,6 +26,10 @@ export class ManifoldPlanDetails { this.features = features; // If plan changed, we want to reset all user-selected values this.updatedPlanHandler({ features }); // Dispatch change event when plan changed } + @Event({ + eventName: 'manifold-planCTA-click', + bubbles: true, + }) ctaClicked: EventEmitter; componentWillLoad() { const features = this.initialFeatures(); @@ -59,6 +62,21 @@ export class ManifoldPlanDetails { }); } + get ctaLink() { + if (!this.product || !this.plan) return undefined; + if (typeof this.linkFormat !== 'string') return undefined; + const params = new URLSearchParams(); + if (Object.keys(this.features)) { + Object.entries(this.features).forEach(([key, value]) => { + params.append(key, value.toString()); + }); + } + return this.linkFormat + .replace(/:product/ig, this.product.body.label) + .replace(/:plan/ig, this.plan.body.label) + .replace(/:features/ig, params.toString()) + } + get header() { if (!this.product || !this.plan) return null; @@ -101,8 +119,6 @@ export class ManifoldPlanDetails { get footer() { if (!this.product || !this.plan) return null; - - const { label: productLabel } = this.product.body; const { name, expanded_features = [] } = this.plan.body; return (