From de00b90605f1b07d00b21249ea73c98a5b651e70 Mon Sep 17 00:00:00 2001 From: AndreiaPena Date: Thu, 16 Jan 2025 17:19:06 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20pix-ui:=20create=20pix=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eric Lim --- addon/components/pix-code.hbs | 31 ++++++ addon/components/pix-code.js | 24 +++++ addon/styles/_pix-code.scss | 37 +++++++ addon/styles/addon.scss | 1 + app/components/pix-code.js | 1 + app/stories/pix-code.mdx | 38 +++++++ app/stories/pix-code.stories.js | 102 ++++++++++++++++++ app/stories/pix-input-code.stories.js | 2 +- tests/integration/components/pix-code-test.js | 99 +++++++++++++++++ 9 files changed, 334 insertions(+), 1 deletion(-) create mode 100644 addon/components/pix-code.hbs create mode 100644 addon/components/pix-code.js create mode 100644 addon/styles/_pix-code.scss create mode 100644 app/components/pix-code.js create mode 100644 app/stories/pix-code.mdx create mode 100644 app/stories/pix-code.stories.js create mode 100644 tests/integration/components/pix-code-test.js diff --git a/addon/components/pix-code.hbs b/addon/components/pix-code.hbs new file mode 100644 index 000000000..27e093760 --- /dev/null +++ b/addon/components/pix-code.hbs @@ -0,0 +1,31 @@ +
+ {{#if (has-block "label")}} + + {{yield to="label"}} + + {{/if}} +
+ + + {{#if this.hasErrorMessage}} +

{{@errorMessage}}

+ {{/if}} +
+
\ No newline at end of file diff --git a/addon/components/pix-code.js b/addon/components/pix-code.js new file mode 100644 index 000000000..8d1bb4a8b --- /dev/null +++ b/addon/components/pix-code.js @@ -0,0 +1,24 @@ +import { warn } from '@ember/debug'; +import { htmlSafe } from '@ember/template'; + +import PixInputBase from './pix-input-base'; + +export default class PixCode extends PixInputBase { + constructor() { + super(...arguments); + + this.prefix = 'pix-code'; + } + + get length() { + warn('PixCode: @length is required.', !['', null, undefined].includes(this.args.length), { + id: 'pix-ui.code.length.required', + }); + + return this.args.length || 1; + } + + get style() { + return htmlSafe('--nb-characters:' + this.length); + } +} diff --git a/addon/styles/_pix-code.scss b/addon/styles/_pix-code.scss new file mode 100644 index 000000000..5499878c6 --- /dev/null +++ b/addon/styles/_pix-code.scss @@ -0,0 +1,37 @@ +@use "pix-design-tokens/typography"; +@use "component-state/form"; + +.pix-code { + @extend %pix-monospace; + @extend %pix-form-element-state; + + $space-between-dashes: 0.6ch; + $total-width: calc(var(--nb-characters) * (1ch + $space-between-dashes)); + + display: inline-block; + box-sizing: content-box; + width: $total-width; + height: 3.125rem; + padding: 0 0.2ch 1px 0.5ch; + color: var(--pix-neutral-900); + font-size: 1.875rem; + letter-spacing: $space-between-dashes; + text-transform: uppercase; + background: repeating-linear-gradient( + 90deg, + var(--pix-neutral-500) 0, + var(--pix-neutral-500) 1ch, + transparent 0, + transparent 1ch + $space-between-dashes + ) + 0 100% / calc($total-width - $space-between-dashes) 1px no-repeat; + background-position-x: 0.5ch; + background-position-y: 2.5ch; + border: solid 1px var(--pix-neutral-500); + border-radius: var(--pix-spacing-1x); + outline: none; +} + +.pix-code__error-message { + @extend %pix-input-error-message; +} diff --git a/addon/styles/addon.scss b/addon/styles/addon.scss index efc22722a..d9b1bbbab 100644 --- a/addon/styles/addon.scss +++ b/addon/styles/addon.scss @@ -45,6 +45,7 @@ @use 'pix-navigation-separator'; @use 'pix-app-layout'; @use 'pix-structure-switcher'; +@use 'pix-code'; // at the end so it can override it's children scss diff --git a/app/components/pix-code.js b/app/components/pix-code.js new file mode 100644 index 000000000..6344bafe1 --- /dev/null +++ b/app/components/pix-code.js @@ -0,0 +1 @@ +export { default } from '@1024pix/pix-ui/components/pix-code'; diff --git a/app/stories/pix-code.mdx b/app/stories/pix-code.mdx new file mode 100644 index 000000000..c1660d7ff --- /dev/null +++ b/app/stories/pix-code.mdx @@ -0,0 +1,38 @@ +import { Meta, Story, ArgTypes } from '@storybook/blocks'; + +import * as ComponentStories from './pix-code.stories.js'; + + + +# Pix Code + +Une `PixCode` est un champ permettant de renseigner un code. Le nombre de caractères attendu est visible par le nombre de traits dans le champ. + + +## Without label + + + +## With label (and subLabel) + + + +## Error state (with error message) + + + +## Usage + +```html + + <:label>Code campagne + +``` + +## Arguments + + diff --git a/app/stories/pix-code.stories.js b/app/stories/pix-code.stories.js new file mode 100644 index 000000000..8730bb184 --- /dev/null +++ b/app/stories/pix-code.stories.js @@ -0,0 +1,102 @@ +import { hbs } from 'ember-cli-htmlbars'; + +export default { + title: 'Forms/Code', + argTypes: { + id: { + name: 'id', + description: 'Identifiant du champ permettant de lui attacher un label', + type: { name: 'string', required: true }, + }, + length: { + name: 'length', + description: 'Correspond au nombre de caractères attendu dans le champ', + type: { name: 'number', required: true }, + table: { + defaultValue: { summary: 1 }, + }, + }, + validationStatus: { + name: 'validationStatus', + description: + "Définit l'état du champ, neutre par défaut ou en erreur selon l'action de l'utilisateur", + type: { name: 'string', required: false }, + options: ['default', 'error'], + control: { type: 'select' }, + }, + errorMessage: { + name: 'errorMessage', + description: + "Affiche le message d'erreur donné. Doit s'accompagner du paramètre validationStatus en 'error'", + type: { name: 'string', required: false }, + }, + label: { + name: 'label', + description: 'Le label du champ', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + }, + }, + subLabel: { + name: 'subLabel', + description: 'Un descriptif complétant le label', + type: { name: 'string', required: false }, + }, + requiredLabel: { + name: 'requiredLabel', + description: 'Label indiquant que le champ est requis.', + type: { name: 'string', required: false }, + table: { + type: { summary: 'string' }, + }, + }, + screenReaderOnly: { + name: 'screenReaderOnly', + description: "Permet de rendre le label lisible uniquement par les lecteurs d'écran", + control: { type: 'boolean' }, + type: { name: 'boolean', required: false }, + table: { + type: { summary: 'boolean' }, + defaultValue: { summary: false }, + }, + }, + }, +}; + +const Template = (args) => { + return { + template: hbs` + <:label>{{this.label}} +`, + context: args, + }; +}; + +export const WithoutLabel = Template.bind({}); +WithoutLabel.args = { + length: 10, +}; + +export const WithLabel = Template.bind({}); +WithLabel.args = { + length: 8, + label: 'Code de vérification', + subLabel: 'Exemple: P-XXXXXXXX', +}; + +export const Error = Template.bind({}); +Error.args = { + length: 6, + label: 'Code de certification', + errorMessage: "un message d'erreur", + validationStatus: 'error', +}; diff --git a/app/stories/pix-input-code.stories.js b/app/stories/pix-input-code.stories.js index 24114db36..34955f53c 100644 --- a/app/stories/pix-input-code.stories.js +++ b/app/stories/pix-input-code.stories.js @@ -1,7 +1,7 @@ import { hbs } from 'ember-cli-htmlbars'; export default { - title: 'Forms/Code', + title: 'Forms/Input Code', argTypes: { ariaLabel: { name: 'ariaLabel', diff --git a/tests/integration/components/pix-code-test.js b/tests/integration/components/pix-code-test.js new file mode 100644 index 000000000..24fe73de6 --- /dev/null +++ b/tests/integration/components/pix-code-test.js @@ -0,0 +1,99 @@ +import { render } from '@1024pix/ember-testing-library'; +import { fillIn } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupRenderingTest } from 'ember-qunit'; +import { module, test } from 'qunit'; +import sinon from 'sinon'; + +module('Integration | Component | PixCode', function (hooks) { + setupRenderingTest(hooks); + + test('it renders the default PixCode', async function (assert) { + // given + const screen = await render( + hbs`<:label>Code de vérification`, + ); + + // when + await fillIn(screen.getByRole('textbox', { name: 'Code de vérification' }), 'P-123VALID'); + + // then + assert.contains('P-123VALID'); + }); + + test('it should be possible to provide the expected number of length in the field', async function (assert) { + // given + const screen = await render( + hbs`<:label>Code de vérification`, + ); + + // when + await fillIn(screen.getByRole('textbox', { name: 'Code de vérification' }), 'P-123VALID'); + + // then + assert + .dom(screen.getByRole('textbox', { name: 'Code de vérification' })) + .hasAttribute('maxLength', '14'); + assert + .dom(screen.getByRole('textbox', { name: 'Code de vérification' })) + .hasAttribute('minLength', '14'); + }); + + test('it should be possible to give an extra information to input', async function (assert) { + // given & when + const screen = await render( + hbs`<:label>Code de vérification`, + ); + + // then + assert.ok(screen.getByRole('textbox', { name: 'Code de vérification exemple: P-1234' })); + }); + + test('it should be possible to give an error message to input', async function (assert) { + // given & when + const screen = await render( + hbs`<:label>Code de vérification`, + ); + + // then + assert.dom(screen.getByText('Seul les caractères alphanumériques sont autorisés')).exists(); + }); + + test('it should be possible to make "pixCode" required', async function (assert) { + // given & when + const screen = await render( + hbs`<:label>Code de vérification`, + ); + + // then + const requiredInput = screen.getByLabelText('Code de vérification *'); + assert.dom(requiredInput).isRequired(); + }); + + module('@length', function (hooks) { + let warnStub; + + hooks.beforeEach(function () { + warnStub = sinon.stub(console, 'warn'); + }); + + hooks.afterEach(function () { + warnStub.restore(); + }); + + ['', null, undefined].forEach(function (testCase) { + test(`it should warn when @length="${testCase}"`, async function (assert) { + // given + this.set('length', testCase); + await render( + hbs`<:label>Code de vérification`, + ); + + assert.ok(warnStub.calledWithExactly('WARNING: PixCode: @length is required.')); + }); + }); + }); +});