Compare commits
2 Commits
e2e90d5512
...
e9082b90ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9082b90ee | ||
|
|
aea8f5675d |
@@ -16,6 +16,7 @@ import type { Copy } from '../core/copy';
|
||||
import type { Locale } from '../core/api';
|
||||
import type { PublicCall } from '../core/api.types';
|
||||
import { TechnologyChip } from './technology-chip';
|
||||
import { LinkButton } from './link-button';
|
||||
|
||||
@Component({
|
||||
selector: 'app-call-card',
|
||||
@@ -32,6 +33,7 @@ import { TechnologyChip } from './technology-chip';
|
||||
PiTextColorDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
TechnologyChip,
|
||||
LinkButton,
|
||||
],
|
||||
template: `
|
||||
<pi-card
|
||||
@@ -96,15 +98,13 @@ import { TechnologyChip } from './technology-chip';
|
||||
<!-- To the call's own page and its form when it has one; to the
|
||||
landing's general form otherwise. -->
|
||||
@if (call().path; as path) {
|
||||
<a class="landing-card__button-link" [href]="path">
|
||||
<pi-action-button
|
||||
size="small"
|
||||
color="primary"
|
||||
appearance="outline"
|
||||
[hasIcon]="true"
|
||||
[text]="copy().labels.apply"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
class="landing-card__button-link"
|
||||
size="small"
|
||||
appearance="outline"
|
||||
[href]="path"
|
||||
[text]="copy().labels.apply"
|
||||
/>
|
||||
} @else {
|
||||
<pi-action-button
|
||||
size="small"
|
||||
|
||||
@@ -4,11 +4,12 @@ import {
|
||||
computed,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { PiActionButtonComponent, PiTextColorDirective } from '@pi/ui';
|
||||
import { PiTextColorDirective } from '@pi/ui';
|
||||
|
||||
import type { Locale } from '../core/api';
|
||||
import type { Copy } from '../core/copy';
|
||||
import { homeHref } from '../core/routes';
|
||||
import { LinkButton } from './link-button';
|
||||
|
||||
/**
|
||||
* "Shall we talk?" at the end of a page's side column, so no page is a dead
|
||||
@@ -17,20 +18,18 @@ import { homeHref } from '../core/routes';
|
||||
@Component({
|
||||
selector: 'app-contact-cta',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [PiActionButtonComponent, PiTextColorDirective],
|
||||
imports: [PiTextColorDirective, LinkButton],
|
||||
template: `
|
||||
<div class="landing-card contact-cta">
|
||||
<h2 class="contact-cta__title">{{ copy().page.ctaTitle }}</h2>
|
||||
<p pi-color color="grey">{{ copy().page.ctaLead }}</p>
|
||||
<!-- An anchor around the library's button, which renders a <button>:
|
||||
a real link, so it works before hydration and opens in a new tab. -->
|
||||
<a class="contact-cta__link" [href]="href()">
|
||||
<pi-action-button
|
||||
color="primary"
|
||||
[text]="copy().nav.contact"
|
||||
[hasIcon]="true"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
class="contact-cta__link"
|
||||
[href]="href()"
|
||||
[text]="copy().nav.contact"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
styles: `
|
||||
|
||||
@@ -130,3 +130,26 @@
|
||||
font-size: var(--pi-font-size-x-small, 0.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
// TEMPORARY. Remove when @pi/ui ships the fix.
|
||||
//
|
||||
// `pi-number-field` declares `<ng-container matPrefix>` unconditionally, so
|
||||
// Material marks the field as having an icon prefix even though nothing is
|
||||
// projected into it. The empty prefix is 4px wide and takes the place of the
|
||||
// 16px inset every other field gets: the value ended up 12px to the left of
|
||||
// its own label, which is what makes it look wrong.
|
||||
//
|
||||
// Only two of the library's twenty fields declare a prefix, both numeric, and
|
||||
// only `incremental-number-field` actually uses one — so this is an oversight
|
||||
// rather than a pattern. The fix belongs there and is written:
|
||||
// `pireon-ui`, `number-field.component.html`, guarded by a `contentChild`.
|
||||
// This restores the inset here until that version is published.
|
||||
:host ::ng-deep .pi-form-field__number {
|
||||
.mat-mdc-form-field-icon-prefix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mat-mdc-form-field-infix {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
79
landing/src/app/ui/link-button.scss
Normal file
79
landing/src/app/ui/link-button.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
// `@pi/ui` scopes its button rules to the element (`html button:not(…)` in
|
||||
// `_pi-button.scss`), so an anchor cannot inherit them. Every value below is
|
||||
// still the library's: these are the same `--pi-theme-button-*` custom
|
||||
// properties its own rules read, so switching shell or theme moves this button
|
||||
// too, and dark mode needs nothing of its own.
|
||||
.link-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-width: 5.5rem;
|
||||
border: none;
|
||||
border-radius: var(--pi-theme-button-radius);
|
||||
line-height: 100%;
|
||||
text-decoration: none;
|
||||
letter-spacing: var(--pi-theme-button-letter-spacing);
|
||||
text-transform: var(--pi-theme-button-text-transform);
|
||||
font-weight: var(--pi-theme-button-font-weight);
|
||||
transition: var(--pi-theme-button-transition);
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
flex: none;
|
||||
width: 0.375rem;
|
||||
height: 0.5625rem;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--pi-color-primary);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.link-button--medium,
|
||||
.link-button--large {
|
||||
min-height: var(--pi-theme-button-height);
|
||||
padding: var(--pi-spacing-xx-small) var(--pi-spacing-x-large);
|
||||
}
|
||||
|
||||
.link-button--small {
|
||||
min-width: 4.5rem;
|
||||
padding: var(--pi-spacing-xxxx-small) var(--pi-spacing-medium);
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
// Filled. The theme may inject a gradient over the flat colour, exactly as it
|
||||
// does for a real button.
|
||||
.link-button--fill {
|
||||
color: var(--pi-color-white);
|
||||
box-shadow: var(--pi-theme-button-shadow);
|
||||
|
||||
&.link-button--primary {
|
||||
background-color: var(--pi-color-primary);
|
||||
background-image: var(--pi-theme-button-fill-primary, none);
|
||||
}
|
||||
|
||||
&.link-button--grey {
|
||||
background-color: var(--pi-color-grey);
|
||||
}
|
||||
}
|
||||
|
||||
// Outline. The colour is the border and the text; the surface stays clear.
|
||||
.link-button--outline {
|
||||
background: none;
|
||||
|
||||
&.link-button--primary {
|
||||
border: 1px solid var(--pi-color-primary);
|
||||
color: var(--pi-color-primary);
|
||||
}
|
||||
|
||||
&.link-button--grey {
|
||||
border: 1px solid var(--pi-color-grey-200);
|
||||
color: var(--pi-color-grey);
|
||||
}
|
||||
}
|
||||
73
landing/src/app/ui/link-button.ts
Normal file
73
landing/src/app/ui/link-button.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
||||
|
||||
/**
|
||||
* A link that looks like the library's action button.
|
||||
*
|
||||
* `pi-action-button` renders a `<button>`, so wrapping one in an `<a>` puts a
|
||||
* button inside a link: invalid HTML, and the button swallows the click, so
|
||||
* nothing navigates. That is what stopped "Web oficial" working, and the same
|
||||
* pattern sat in five other places.
|
||||
*
|
||||
* Making it an anchor is not only about the click. A real link can be opened
|
||||
* in a new tab, middle-clicked, copied, reached by keyboard and followed by a
|
||||
* crawler; a button can do none of that.
|
||||
*
|
||||
* The library's own button classes are deliberately NOT reused. Its rules are
|
||||
* scoped to the element — `html button:not(…)` — so on an anchor they
|
||||
* contribute nothing except `pi-color__primary`, which is a *text* colour and
|
||||
* actively fights the white a filled button needs. The stylesheet beside this
|
||||
* file reads the same `--pi-theme-button-*` tokens the library reads instead,
|
||||
* so a change of shell or theme still reaches this button.
|
||||
*/
|
||||
type Size = 'small' | 'medium' | 'large';
|
||||
type Tone = 'primary' | 'grey';
|
||||
|
||||
@Component({
|
||||
selector: 'app-link-button',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
styleUrl: './link-button.scss',
|
||||
template: `
|
||||
<a
|
||||
[class]="classes()"
|
||||
[href]="href()"
|
||||
[attr.target]="external() ? '_blank' : null"
|
||||
[attr.rel]="external() ? 'noopener noreferrer' : null"
|
||||
>
|
||||
<span>{{ text() }}</span>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 6 9"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M1 1C4.5 4 5 4.38484 5 4.5C5 4.61516 4.5 5 1 8"
|
||||
stroke="currentColor"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M4.5 4.5H0" stroke="currentColor" stroke-width="1" />
|
||||
</g>
|
||||
</svg>
|
||||
</a>
|
||||
`,
|
||||
})
|
||||
export class LinkButton {
|
||||
readonly href = input.required<string>();
|
||||
readonly text = input.required<string>();
|
||||
readonly size = input<Size>('medium');
|
||||
readonly color = input<Tone>('primary');
|
||||
readonly appearance = input<'fill' | 'outline'>('fill');
|
||||
/** Opens in a new tab, for addresses that are not ours. */
|
||||
readonly external = input(false);
|
||||
|
||||
protected readonly classes = computed(() => [
|
||||
'link-button',
|
||||
`link-button--${this.size()}`,
|
||||
`link-button--${this.color()}`,
|
||||
`link-button--${this.appearance()}`,
|
||||
]);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { LinkButton } from './link-button';
|
||||
import {
|
||||
PiActionButtonComponent,
|
||||
PiIconComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextMediumDirective,
|
||||
@@ -26,12 +26,12 @@ import {
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [
|
||||
PiIconComponent,
|
||||
PiActionButtonComponent,
|
||||
PiTextXLargeDirective,
|
||||
PiTextXLargeDirective,
|
||||
PiTextMediumDirective,
|
||||
PiTextSmallDirective,
|
||||
PiTextColorDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
LinkButton,
|
||||
],
|
||||
styleUrl: './not-found.scss',
|
||||
template: `
|
||||
@@ -47,14 +47,12 @@ import {
|
||||
La dirección puede haber cambiado. Vuelve al inicio y sigue desde ahí.
|
||||
</p>
|
||||
|
||||
<a class="not-found__home" href="/">
|
||||
<pi-action-button
|
||||
size="large"
|
||||
color="primary"
|
||||
text="Ir al inicio"
|
||||
[hasIcon]="true"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
class="not-found__home"
|
||||
size="large"
|
||||
href="/"
|
||||
text="Ir al inicio"
|
||||
/>
|
||||
|
||||
<p s pi-color color="grey" class="not-found__note">
|
||||
Si has llegado desde un enlace nuestro, cuéntanoslo.
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
PiActionButtonComponent,
|
||||
PiAvatarComponent,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
@@ -21,6 +20,7 @@ import { ApiImage } from './api-image';
|
||||
import { ContactCta } from './contact-cta';
|
||||
import { PROJECT_STATUS_TONES } from './project-card';
|
||||
import { TechnologyChip } from './technology-chip';
|
||||
import { LinkButton } from './link-button';
|
||||
|
||||
/**
|
||||
* A project on its own address: what it is, why it was built the way it was,
|
||||
@@ -35,14 +35,14 @@ import { TechnologyChip } from './technology-chip';
|
||||
imports: [
|
||||
ApiImage,
|
||||
ContactCta,
|
||||
PiActionButtonComponent,
|
||||
PiAvatarComponent,
|
||||
PiAvatarComponent,
|
||||
PiChipComponent,
|
||||
PiChipStatusComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextLargeDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
TechnologyChip,
|
||||
LinkButton,
|
||||
],
|
||||
styleUrl: './project-detail.scss',
|
||||
template: `
|
||||
@@ -92,23 +92,20 @@ import { TechnologyChip } from './technology-chip';
|
||||
@if (project().demo_url || repositoryUrl()) {
|
||||
<div class="content-hero__actions">
|
||||
@if (project().demo_url; as demo) {
|
||||
<a [href]="demo" target="_blank" rel="noopener">
|
||||
<pi-action-button
|
||||
color="primary"
|
||||
[text]="copy().labels.demo"
|
||||
[hasIcon]="true"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
[href]="demo"
|
||||
[text]="copy().labels.demo"
|
||||
[external]="true"
|
||||
/>
|
||||
}
|
||||
@if (repositoryUrl(); as repository) {
|
||||
<a [href]="repository" target="_blank" rel="noopener">
|
||||
<pi-action-button
|
||||
appearance="outline"
|
||||
color="grey"
|
||||
[text]="copy().labels.repository"
|
||||
[hasIcon]="true"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
color="grey"
|
||||
appearance="outline"
|
||||
[href]="repository"
|
||||
[text]="copy().labels.repository"
|
||||
[external]="true"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
PiActionButtonComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextLargeDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
@@ -15,6 +14,7 @@ import type { Locale } from '../core/api';
|
||||
import type { PublicTechnology } from '../core/api.types';
|
||||
import type { Copy } from '../core/copy';
|
||||
import { listingPath } from '../core/routes';
|
||||
import { LinkButton } from './link-button';
|
||||
import { ApiImage } from './api-image';
|
||||
import { ContactCta } from './contact-cta';
|
||||
|
||||
@@ -36,10 +36,10 @@ import { ContactCta } from './contact-cta';
|
||||
imports: [
|
||||
ApiImage,
|
||||
ContactCta,
|
||||
PiActionButtonComponent,
|
||||
PiTextColorDirective,
|
||||
PiTextColorDirective,
|
||||
PiTextLargeDirective,
|
||||
PiTextWeightBoldDirective,
|
||||
LinkButton,
|
||||
],
|
||||
styleUrl: './technology-detail.scss',
|
||||
template: `
|
||||
@@ -75,14 +75,12 @@ import { ContactCta } from './contact-cta';
|
||||
|
||||
@if (technology().website; as website) {
|
||||
<p class="content-hero__actions">
|
||||
<a [href]="website" target="_blank" rel="noopener noreferrer">
|
||||
<pi-action-button
|
||||
size="large"
|
||||
color="primary"
|
||||
[text]="copy().technology.website"
|
||||
[hasIcon]="true"
|
||||
/>
|
||||
</a>
|
||||
<app-link-button
|
||||
size="large"
|
||||
[href]="website"
|
||||
[text]="copy().technology.website"
|
||||
[external]="true"
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user