@financial-times/o3-form 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,8 +8,10 @@ Provides components to construct forms.
8
8
  - [Form Field, Form Fieldset and Feedback](#form-field-form-fieldset-and-feedback)
9
9
  - [Text Input](#text-input)
10
10
  - [Short text input](#short-text-input)
11
+ - [Password Input](#password-input)
11
12
  - [Checkbox](#checkbox)
12
13
  - [Checkbox Group](#checkbox-group)
14
+ - [Select Input](#select-input)
13
15
  - [Contact](#contact)
14
16
  - [Licence](#licence)
15
17
 
@@ -263,6 +265,73 @@ import {TextInput} from '@financial-times/o3-form/cjs'; // or esm
263
265
  />
264
266
  ```
265
267
 
268
+ #### Password Input
269
+
270
+ A password input for collecting password values. Features a show/hide password toggle and a forgot password link.
271
+
272
+ **HTML**
273
+ ```html
274
+ <div class="o3-form-field">
275
+ <label
276
+ class="o3-form-field__label"
277
+ for="o3-form-password-input-_5538262633951523"
278
+ >
279
+ Password
280
+ </label>
281
+ <span
282
+ class="o3-form-input__description"
283
+ id="o3-form-description_5812824517374977"
284
+ >
285
+ Your password must be at least 8 characters.
286
+ </span>
287
+ <div class="o3-password-input__container">
288
+ <input
289
+ id="o3-form-password-input-_5538262633951523"
290
+ class="o3-form o3-form-text-input o3-form-text-input--password"
291
+ required=""
292
+ type="password"
293
+ aria-required="true"
294
+ />
295
+ <button
296
+ id="o3-form-password-toggle"
297
+ class="o3-password-input__show-password-toggle o3-password-input__show-password-toggle--show"
298
+ aria-label="Show password"
299
+ title="Show password"
300
+ aria-pressed="false"
301
+ ></button>
302
+ </div>
303
+ <div class="o3-form-feedback o3-form-feedback__undefined">
304
+ <span class="o3-form-feedback__undefined-message"></span>
305
+ </div>
306
+ </div>
307
+ <div class="o3-password-input__controls">
308
+ <a class="o3-typography-link" href="#">Forgot password?</a>
309
+ </div>
310
+ ```
311
+
312
+ Be sure to include Javascript to enable the password toggle feature.
313
+
314
+ ```javascript
315
+ import PasswordInput from '@financial-times/o3-form/cjs/PasswordInput';
316
+
317
+ document.addEventListener('DOMContentLoaded', function () {
318
+ const passwordInput = new PasswordInput(
319
+ document.getElementById('o3-form-password-toggle')
320
+ );
321
+ });
322
+ ```
323
+
324
+ ```tsx
325
+ import {PasswordInput} from '@financial-times/o3-form';
326
+
327
+ <Form>
328
+ <PasswordInput
329
+ label="Password"
330
+ description="Your password must be at least 8 characters."
331
+ forgotPasswordLink="#" />
332
+ </Form>
333
+ ```
334
+
266
335
  ### Checkbox
267
336
 
268
337
  A customizable and accessible checkbox input for selecting one or more items from a list, or turning an item on or off. Checkbox can also have a state of `indeterminate` and `o3-form` provides styling, for `indeterminate` state but this state can only be set using javaScript. Read more about [indeterminate state](https://css-tricks.com/indeterminate-checkboxes/).
@@ -384,6 +453,61 @@ import {CheckBoxGroup, CheckBoxItem} from '@financial-times/o3-form/cjs'; // or
384
453
  | `description` | A description of the group of checkboxes | string | |
385
454
  | `children` | The checkboxes to be rendered | CheckBox[] | |
386
455
 
456
+ ### Select Input
457
+
458
+ A dropdown select input for choosing one option from a list.
459
+
460
+ ```html
461
+ <div class="o3-form-field">
462
+ <label
463
+ class="o3-form-field__label"
464
+ for="o3-form-select-input-_3564083419195512"
465
+ >
466
+ Card type
467
+ </label>
468
+ <span
469
+ class="o3-form-input__description"
470
+ id="o3-form-description_14471165011746046"
471
+ >
472
+ Printed on the front side of your payment card.
473
+ </span>
474
+ <div class="o3-form-select-input__container">
475
+ <select
476
+ id="o3-form-select-input-_3564083419195512"
477
+ class="o3-form o3-form-select-input"
478
+ required=""
479
+ aria-required="true"
480
+ maxlength="0"
481
+ type="select"
482
+ >
483
+ <option value="American Express">American Express</option>
484
+ <option value="Visa Debit">Visa Debit</option>
485
+ <option value="Visa Credit">Visa Credit</option>
486
+ <option value="Mastercard Debit">Mastercard Debit</option>
487
+ <option value="Mastercard Credit">Mastercard Credit</option>
488
+ </select>
489
+ </div>
490
+ <div class="o3-form-feedback o3-form-feedback__undefined">
491
+ <span class="o3-form-feedback__undefined-message"></span>
492
+ </div>
493
+ </div>
494
+ ```
495
+
496
+ ```tsx
497
+ import {SelectInput} from '@financial-times/o3-form';
498
+
499
+ <SelectInput
500
+ label="Card type"
501
+ description="Printed on the front side of your payment card."
502
+ >
503
+ <option value="American Express">American Express</option>
504
+ <option value="Visa Debit">Visa Debit</option>
505
+ <option value="Visa Credit">Visa Credit</option>
506
+ <option value="Mastercard Debit">Mastercard Debit</option>
507
+ <option value="Mastercard Credit">Mastercard Credit</option>
508
+ </SelectInput>
509
+ ```
510
+
387
511
  ## Contact
388
512
 
389
513
  If you have any questions or comments about this component, or need help using it, please either [raise an issue](https://github.com/Financial-Times/o3-editorial-typography/issues), visit [#origami-support](https://financialtimes.slack.com/messages/origami-support/) or email [Origami Support](mailto:origami-support@ft.com).
package/cjs/CheckBox.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CheckBoxProps, F as FormFieldsetProps } from './index-CufC9u5W.js';
2
+ import { C as CheckBoxProps, F as FormFieldsetProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const CheckBoxItem: (props: CheckBoxProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const CheckBox: (props: CheckBoxProps) => react_jsx_runtime.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { E as ErrorSummaryProps } from './index-CufC9u5W.js';
2
+ import { E as ErrorSummaryProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const ErrorSummary: ({ errors, errorMessage, }: ErrorSummaryProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { P as PasswordInputProps } from './index-CufC9u5W.js';
2
+ import { P as PasswordInputProps } from './index-B5zd_nrf.js';
3
3
 
4
- declare const PasswordInput: ({ label, feedback, description, disabled, attributes, inputId, optional, }: PasswordInputProps) => react_jsx_runtime.JSX.Element;
4
+ declare const PasswordInput: ({ label, feedback, description, disabled, attributes, inputId, optional, forgotPasswordLink, }: PasswordInputProps) => react_jsx_runtime.JSX.Element;
5
5
 
6
6
  export { PasswordInput };
@@ -1,7 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _jsxruntime = require('react/jsx-runtime');
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _jsxruntime = require('react/jsx-runtime');
2
2
  var _outils = require('@financial-times/o-utils');
3
3
  var _FormField = require('./fieldComponents/FormField');
4
- var _CheckBox = require('./CheckBox');
4
+ var _PasswordInputjs = require('../js/PasswordInput.js');
5
+ var _react = require('react');
5
6
  const uniqueId = _outils.uidBuilder.call(void 0, "o3-form-password-input");
6
7
  const PasswordInput = ({
7
8
  label,
@@ -10,14 +11,34 @@ const PasswordInput = ({
10
11
  disabled,
11
12
  attributes,
12
13
  inputId,
13
- optional
14
+ optional,
15
+ forgotPasswordLink
14
16
  }) => {
15
17
  const id = inputId || uniqueId("_");
16
- const showPasswordId = `${id}-showPassword`;
17
- const inputClasses = ["o3-form", "o3-form-text-input"];
18
+ let inputRef = _react.useRef.call(void 0, null);
19
+ let buttonRef = _react.useRef.call(void 0, null);
20
+ let toggleRef = _react.useRef.call(void 0,
21
+ null
22
+ );
23
+ const inputClasses = [
24
+ "o3-form",
25
+ "o3-form-text-input",
26
+ "o3-form-text-input--password"
27
+ ];
18
28
  if (feedback && feedback.type === "error") {
19
29
  inputClasses.push("o3-form-text-input--error");
20
30
  }
31
+ _react.useEffect.call(void 0, () => {
32
+ const inputEl = inputRef.current;
33
+ const btnEl = buttonRef.current;
34
+ if (!inputEl || !btnEl) return;
35
+ toggleRef.current = new (0, _PasswordInputjs.PasswordInput)(inputEl, {
36
+ toggleButtonId: btnEl.id
37
+ });
38
+ return () => {
39
+ toggleRef.current = null;
40
+ };
41
+ }, []);
21
42
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
22
43
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
23
44
  _FormField.LabeledFormField,
@@ -27,31 +48,40 @@ const PasswordInput = ({
27
48
  description,
28
49
  inputId: id,
29
50
  optional,
30
- children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
31
- "input",
32
- {
33
- ...attributes,
34
- id,
35
- disabled,
36
- className: inputClasses.join(" "),
37
- required: !optional,
38
- "aria-required": !optional,
39
- type: "password"
40
- }
41
- )
51
+ children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "o3-password-input__container", children: [
52
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
53
+ "input",
54
+ {
55
+ ...attributes,
56
+ id,
57
+ ref: inputRef,
58
+ disabled,
59
+ "data-testid": "o3-password-input",
60
+ className: inputClasses.join(" "),
61
+ required: !optional,
62
+ type: "password",
63
+ "aria-required": !optional
64
+ }
65
+ ),
66
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
67
+ "button",
68
+ {
69
+ id: "o3-form-password-toggle",
70
+ className: "o3-password-input__show-password-toggle o3-password-input__show-password-toggle--show",
71
+ ref: buttonRef,
72
+ "data-testid": "o3-password-input-toggle",
73
+ "aria-controls": inputId,
74
+ "aria-label": "Show password",
75
+ title: "Show password",
76
+ onClick: () => {
77
+ _optionalChain([toggleRef, 'access', _ => _.current, 'optionalAccess', _2 => _2.toggle, 'call', _3 => _3()]);
78
+ }
79
+ }
80
+ )
81
+ ] })
42
82
  }
43
83
  ),
44
- /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "o3-password-input__controls", children: [
45
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
46
- _CheckBox.CheckBoxItem,
47
- {
48
- attributes: { disabled },
49
- inputId: showPasswordId,
50
- checkboxLabel: "Show password"
51
- }
52
- ),
53
- !disabled && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { className: "o3-typography-link", href: "", children: "Forgot password?" })
54
- ] })
84
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "o3-password-input__controls", children: !disabled && forgotPasswordLink && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "a", { className: "o3-typography-link", href: forgotPasswordLink, children: "Forgot password?" }) })
55
85
  ] });
56
86
  };
57
87
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { R as RadioButtonProps, F as FormFieldsetProps } from './index-CufC9u5W.js';
2
+ import { R as RadioButtonProps, F as FormFieldsetProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const RadioButtonItem: (props: RadioButtonProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const RadioButtonGroup: (props: FormFieldsetProps) => react_jsx_runtime.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { S as SelectInputProps } from './index-B5zd_nrf.js';
3
+
4
+ declare const SelectInput: ({ label, feedback, description, disabled, attributes, inputId, optional, children, }: SelectInputProps) => react_jsx_runtime.JSX.Element;
5
+
6
+ export { SelectInput };
@@ -0,0 +1,45 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _jsxruntime = require('react/jsx-runtime');
2
+ var _outils = require('@financial-times/o-utils');
3
+ var _FormField = require('./fieldComponents/FormField');
4
+ const uniqueId = _outils.uidBuilder.call(void 0, "o3-form-select-input");
5
+ const SelectInput = ({
6
+ label,
7
+ feedback,
8
+ description,
9
+ disabled,
10
+ attributes,
11
+ inputId,
12
+ optional,
13
+ children
14
+ }) => {
15
+ const id = inputId || uniqueId("_");
16
+ const inputClasses = ["o3-form", "o3-form-select-input"];
17
+ if (feedback && feedback.type === "error") {
18
+ inputClasses.push("o3-form-select-input--error");
19
+ }
20
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
21
+ _FormField.LabeledFormField,
22
+ {
23
+ label,
24
+ feedback,
25
+ description,
26
+ inputId: id,
27
+ optional,
28
+ children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "o3-form-select-input__container", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
29
+ "select",
30
+ {
31
+ ...attributes,
32
+ id,
33
+ disabled,
34
+ className: inputClasses.join(" "),
35
+ required: !optional,
36
+ "aria-required": !optional,
37
+ children
38
+ }
39
+ ) })
40
+ }
41
+ );
42
+ };
43
+
44
+
45
+ exports.SelectInput = SelectInput;
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { T as TextInputProps } from './index-CufC9u5W.js';
2
+ import { T as TextInputProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const TextInput: ({ label, feedback, description, disabled, length, attributes, inputId, optional, }: TextInputProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { a as FeedbackProps } from '../index-CufC9u5W.js';
2
+ import { a as FeedbackProps } from '../index-B5zd_nrf.js';
3
3
 
4
4
  declare const Feedback: ({ message, type }: FeedbackProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { b as FormFieldProps, F as FormFieldsetProps } from '../index-CufC9u5W.js';
2
+ import { b as FormFieldProps, F as FormFieldsetProps } from '../index-B5zd_nrf.js';
3
3
 
4
4
  declare const LabeledFormField: ({ inputId, label, description, feedback, children, optional, }: FormFieldProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const TitledFormField: ({ label, description, feedback, children, optional, }: FormFieldProps) => react_jsx_runtime.JSX.Element;
@@ -11,9 +11,21 @@ interface TextInputProps extends BaseInputProps {
11
11
  length?: 2 | 3 | 4 | 5;
12
12
  feedback?: FeedbackProps;
13
13
  }
14
+ interface SelectInputProps {
15
+ disabled?: boolean;
16
+ feedback?: FeedbackProps;
17
+ children?: React.JSX.Element[];
18
+ inputId?: string;
19
+ label?: string;
20
+ description?: string;
21
+ optional?: boolean;
22
+ error?: boolean;
23
+ attributes?: JSX.IntrinsicElements['select'];
24
+ }
14
25
  interface PasswordInputProps extends BaseInputProps {
15
26
  disabled?: boolean;
16
27
  feedback?: FeedbackProps;
28
+ forgotPasswordLink?: string;
17
29
  }
18
30
  interface CheckBoxProps extends BaseInputProps {
19
31
  inputId: string;
@@ -50,4 +62,4 @@ type ErrorSummaryProps = {
50
62
  }[];
51
63
  };
52
64
 
53
- export type { CheckBoxProps as C, ErrorSummaryProps as E, FormFieldsetProps as F, PasswordInputProps as P, RadioButtonProps as R, TextInputProps as T, FeedbackProps as a, FormFieldProps as b };
65
+ export type { CheckBoxProps as C, ErrorSummaryProps as E, FormFieldsetProps as F, PasswordInputProps as P, RadioButtonProps as R, SelectInputProps as S, TextInputProps as T, FeedbackProps as a, FormFieldProps as b };
package/cjs/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export { ErrorSummary } from './Error-summary.js';
7
7
  export { PasswordInput } from './PasswordInput.js';
8
8
  export { TextInput } from './TextInput.js';
9
9
  import 'react/jsx-runtime';
10
- import './index-CufC9u5W.js';
10
+ import './index-B5zd_nrf.js';
package/css/main.css CHANGED
@@ -4,7 +4,6 @@
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  justify-content: start;
7
- gap: var(--o3-spacing-s);
8
7
  }
9
8
 
10
9
  /* src/css/components/form-field.css */
@@ -12,7 +11,7 @@
12
11
  display: flex;
13
12
  flex-direction: column;
14
13
  font-family: var(--o3-font-family-metric);
15
- gap: var(--o3-spacing-3xs);
14
+ gap: var(--o3-spacing-xs);
16
15
  }
17
16
  .o3-form-field:where(fieldset) {
18
17
  margin: 0;
@@ -164,6 +163,7 @@ input[class*=o3-form-text-input][type=text]:disabled {
164
163
  }
165
164
  .o3-form-input-checkbox__input:disabled + label::before {
166
165
  border-color: var(--_o3-form-color-use-case-border-disabled);
166
+ background-color: var(--_o3-form-color-use-case-border-disabled);
167
167
  }
168
168
  .o3-form-input-checkbox__input:disabled + label {
169
169
  color: var(--_o3-form-color-use-case-border-disabled);
@@ -294,6 +294,7 @@ input[class*=o3-form-text-input][type=text]:disabled {
294
294
  }
295
295
  .o3-form-input-radio-button:disabled + label::before {
296
296
  border-color: var(--_o3-form-color-use-case-border-disabled);
297
+ background-color: var(--_o3-form-color-use-case-border-disabled);
297
298
  }
298
299
  .o3-form-input-radio-button:disabled:checked + label::after {
299
300
  background-color: var(--_o3-form-color-use-case-border-disabled);
@@ -390,16 +391,97 @@ input[class*=o3-form-text-input][type=text]:disabled {
390
391
  }
391
392
 
392
393
  /* src/css/components/password-input.css */
394
+ .o3-password-input__container {
395
+ width: 100%;
396
+ display: inline-block;
397
+ position: relative;
398
+ --o3-grid-columns-to-span-count: 4;
399
+ max-width: var(--o3-grid-columns-to-span-width, 100%);
400
+ margin-top: var(--o3-spacing-3xs);
401
+ }
402
+ .o3-password-input__container input {
403
+ width: 100%;
404
+ padding-right: 48px;
405
+ }
406
+ .o3-password-input__show-password-toggle {
407
+ width: 24px;
408
+ height: 24px;
409
+ position: absolute;
410
+ right: 14px;
411
+ top: 50%;
412
+ transform: translateY(-50%);
413
+ background-repeat: no-repeat;
414
+ border: none;
415
+ }
416
+ .o3-password-input__show-password-toggle--show {
417
+ background: var(--o3-icon-eye-open);
418
+ }
419
+ .o3-password-input__show-password-toggle--hide {
420
+ background: var(--o3-icon-eye-closed);
421
+ }
393
422
  .o3-password-input__controls {
394
423
  display: flex;
424
+ flex-direction: row-reverse;
395
425
  justify-content: space-between;
396
426
  --o3-grid-columns-to-span-count: 4;
397
427
  max-width: var(--o3-grid-columns-to-span-width, 100%);
398
- margin-top: var(--o3-spacing-3xs);
399
428
  font-family: var(--o3-type-body-base-font-family);
400
429
  font-size: var(--o3-type-body-base-font-size);
401
430
  font-weight: var(--o3-type-body-base-font-weight);
402
431
  line-height: var(--o3-type-body-base-line-height);
403
432
  }
433
+ .o3-password-input__controls > a {
434
+ color: var(--o3-color-use-case-body-text);
435
+ text-decoration-color: unset;
436
+ }
437
+ .o3-form-text-input--password {
438
+ padding-right: var(--o3-spacing-s);
439
+ }
440
+
441
+ /* src/css/components/select-input.css */
442
+ .o3-form-select-input {
443
+ border: var(--_o3-form-input-border);
444
+ padding: var(--o3-spacing-4xs) var(--o3-spacing-l) var(--o3-spacing-4xs) var(--o3-spacing-2xs);
445
+ background: var(--_o3-form-color-use-case-background);
446
+ box-sizing: border-box;
447
+ border-radius: var(--_o3-form-input-border-radius);
448
+ font-family: var(--o3-type-body-lg-font-family);
449
+ font-size: var(--o3-type-body-lg-font-size);
450
+ font-weight: var(--o3-type-body-lg-font-weight);
451
+ line-height: var(--o3-type-body-lg-line-height);
452
+ width: 100%;
453
+ min-height: 44px;
454
+ -webkit-appearance: none;
455
+ appearance: none;
456
+ }
457
+ .o3-form-select-input__container {
458
+ position: relative;
459
+ display: inline-block;
460
+ --o3-grid-columns-to-span-count: 4;
461
+ max-width: var(--o3-grid-columns-to-span-width, 100%);
462
+ }
463
+ .o3-form-select-input__container:after {
464
+ content: "";
465
+ position: absolute;
466
+ right: 8px;
467
+ top: 50%;
468
+ transform: translateY(-50%);
469
+ width: 24px;
470
+ height: 24px;
471
+ mask: var(--o3-icon-chevron-down) no-repeat center;
472
+ -webkit-mask: var(--o3-icon-chevron-down) no-repeat center;
473
+ background-color: var(--o3-color-palette-black-60);
474
+ pointer-events: none;
475
+ }
476
+ .o3-form-select-input--error {
477
+ border: var(--_o3-form-input-border-error);
478
+ background-color: var(--_o3-form-color-use-case-background-error);
479
+ }
480
+ select[class*=o3-form-select-input]:disabled,
481
+ select[class*=o3-form-select-input]:disabled {
482
+ color: var(--o3-color-use-case-muted-text);
483
+ background-color: var(--_o3-form-color-use-case-background-disabled);
484
+ border-color: var(--_o3-form-color-use-case-border-disabled);
485
+ }
404
486
 
405
487
  /* main.css */
package/esm/CheckBox.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CheckBoxProps, F as FormFieldsetProps } from './index-CufC9u5W.js';
2
+ import { C as CheckBoxProps, F as FormFieldsetProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const CheckBoxItem: (props: CheckBoxProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const CheckBox: (props: CheckBoxProps) => react_jsx_runtime.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { E as ErrorSummaryProps } from './index-CufC9u5W.js';
2
+ import { E as ErrorSummaryProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const ErrorSummary: ({ errors, errorMessage, }: ErrorSummaryProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { P as PasswordInputProps } from './index-CufC9u5W.js';
2
+ import { P as PasswordInputProps } from './index-B5zd_nrf.js';
3
3
 
4
- declare const PasswordInput: ({ label, feedback, description, disabled, attributes, inputId, optional, }: PasswordInputProps) => react_jsx_runtime.JSX.Element;
4
+ declare const PasswordInput: ({ label, feedback, description, disabled, attributes, inputId, optional, forgotPasswordLink, }: PasswordInputProps) => react_jsx_runtime.JSX.Element;
5
5
 
6
6
  export { PasswordInput };
@@ -1,7 +1,8 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { uidBuilder } from "@financial-times/o-utils";
3
3
  import { LabeledFormField } from "./fieldComponents/FormField";
4
- import { CheckBoxItem } from "./CheckBox";
4
+ import { PasswordInput as PasswordInputController } from "../js/PasswordInput.js";
5
+ import { useEffect, useRef } from "react";
5
6
  const uniqueId = uidBuilder("o3-form-password-input");
6
7
  const PasswordInput = ({
7
8
  label,
@@ -10,14 +11,34 @@ const PasswordInput = ({
10
11
  disabled,
11
12
  attributes,
12
13
  inputId,
13
- optional
14
+ optional,
15
+ forgotPasswordLink
14
16
  }) => {
15
17
  const id = inputId || uniqueId("_");
16
- const showPasswordId = `${id}-showPassword`;
17
- const inputClasses = ["o3-form", "o3-form-text-input"];
18
+ let inputRef = useRef(null);
19
+ let buttonRef = useRef(null);
20
+ let toggleRef = useRef(
21
+ null
22
+ );
23
+ const inputClasses = [
24
+ "o3-form",
25
+ "o3-form-text-input",
26
+ "o3-form-text-input--password"
27
+ ];
18
28
  if (feedback && feedback.type === "error") {
19
29
  inputClasses.push("o3-form-text-input--error");
20
30
  }
31
+ useEffect(() => {
32
+ const inputEl = inputRef.current;
33
+ const btnEl = buttonRef.current;
34
+ if (!inputEl || !btnEl) return;
35
+ toggleRef.current = new PasswordInputController(inputEl, {
36
+ toggleButtonId: btnEl.id
37
+ });
38
+ return () => {
39
+ toggleRef.current = null;
40
+ };
41
+ }, []);
21
42
  return /* @__PURE__ */ jsxs(Fragment, { children: [
22
43
  /* @__PURE__ */ jsx(
23
44
  LabeledFormField,
@@ -27,31 +48,40 @@ const PasswordInput = ({
27
48
  description,
28
49
  inputId: id,
29
50
  optional,
30
- children: /* @__PURE__ */ jsx(
31
- "input",
32
- {
33
- ...attributes,
34
- id,
35
- disabled,
36
- className: inputClasses.join(" "),
37
- required: !optional,
38
- "aria-required": !optional,
39
- type: "password"
40
- }
41
- )
51
+ children: /* @__PURE__ */ jsxs("div", { className: "o3-password-input__container", children: [
52
+ /* @__PURE__ */ jsx(
53
+ "input",
54
+ {
55
+ ...attributes,
56
+ id,
57
+ ref: inputRef,
58
+ disabled,
59
+ "data-testid": "o3-password-input",
60
+ className: inputClasses.join(" "),
61
+ required: !optional,
62
+ type: "password",
63
+ "aria-required": !optional
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsx(
67
+ "button",
68
+ {
69
+ id: "o3-form-password-toggle",
70
+ className: "o3-password-input__show-password-toggle o3-password-input__show-password-toggle--show",
71
+ ref: buttonRef,
72
+ "data-testid": "o3-password-input-toggle",
73
+ "aria-controls": inputId,
74
+ "aria-label": "Show password",
75
+ title: "Show password",
76
+ onClick: () => {
77
+ toggleRef.current?.toggle();
78
+ }
79
+ }
80
+ )
81
+ ] })
42
82
  }
43
83
  ),
44
- /* @__PURE__ */ jsxs("div", { className: "o3-password-input__controls", children: [
45
- /* @__PURE__ */ jsx(
46
- CheckBoxItem,
47
- {
48
- attributes: { disabled },
49
- inputId: showPasswordId,
50
- checkboxLabel: "Show password"
51
- }
52
- ),
53
- !disabled && /* @__PURE__ */ jsx("a", { className: "o3-typography-link", href: "", children: "Forgot password?" })
54
- ] })
84
+ /* @__PURE__ */ jsx("div", { className: "o3-password-input__controls", children: !disabled && forgotPasswordLink && /* @__PURE__ */ jsx("a", { className: "o3-typography-link", href: forgotPasswordLink, children: "Forgot password?" }) })
55
85
  ] });
56
86
  };
57
87
  export {
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { R as RadioButtonProps, F as FormFieldsetProps } from './index-CufC9u5W.js';
2
+ import { R as RadioButtonProps, F as FormFieldsetProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const RadioButtonItem: (props: RadioButtonProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const RadioButtonGroup: (props: FormFieldsetProps) => react_jsx_runtime.JSX.Element;
@@ -0,0 +1,6 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { S as SelectInputProps } from './index-B5zd_nrf.js';
3
+
4
+ declare const SelectInput: ({ label, feedback, description, disabled, attributes, inputId, optional, children, }: SelectInputProps) => react_jsx_runtime.JSX.Element;
5
+
6
+ export { SelectInput };
@@ -0,0 +1,45 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { uidBuilder } from "@financial-times/o-utils";
3
+ import { LabeledFormField } from "./fieldComponents/FormField";
4
+ const uniqueId = uidBuilder("o3-form-select-input");
5
+ const SelectInput = ({
6
+ label,
7
+ feedback,
8
+ description,
9
+ disabled,
10
+ attributes,
11
+ inputId,
12
+ optional,
13
+ children
14
+ }) => {
15
+ const id = inputId || uniqueId("_");
16
+ const inputClasses = ["o3-form", "o3-form-select-input"];
17
+ if (feedback && feedback.type === "error") {
18
+ inputClasses.push("o3-form-select-input--error");
19
+ }
20
+ return /* @__PURE__ */ jsx(
21
+ LabeledFormField,
22
+ {
23
+ label,
24
+ feedback,
25
+ description,
26
+ inputId: id,
27
+ optional,
28
+ children: /* @__PURE__ */ jsx("div", { className: "o3-form-select-input__container", children: /* @__PURE__ */ jsx(
29
+ "select",
30
+ {
31
+ ...attributes,
32
+ id,
33
+ disabled,
34
+ className: inputClasses.join(" "),
35
+ required: !optional,
36
+ "aria-required": !optional,
37
+ children
38
+ }
39
+ ) })
40
+ }
41
+ );
42
+ };
43
+ export {
44
+ SelectInput
45
+ };
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { T as TextInputProps } from './index-CufC9u5W.js';
2
+ import { T as TextInputProps } from './index-B5zd_nrf.js';
3
3
 
4
4
  declare const TextInput: ({ label, feedback, description, disabled, length, attributes, inputId, optional, }: TextInputProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { a as FeedbackProps } from '../index-CufC9u5W.mjs';
2
+ import { a as FeedbackProps } from '../index-B5zd_nrf.mjs';
3
3
 
4
4
  declare const Feedback: ({ message, type }: FeedbackProps) => react_jsx_runtime.JSX.Element;
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { b as FormFieldProps, F as FormFieldsetProps } from '../index-CufC9u5W.mjs';
2
+ import { b as FormFieldProps, F as FormFieldsetProps } from '../index-B5zd_nrf.mjs';
3
3
 
4
4
  declare const LabeledFormField: ({ inputId, label, description, feedback, children, optional, }: FormFieldProps) => react_jsx_runtime.JSX.Element;
5
5
  declare const TitledFormField: ({ label, description, feedback, children, optional, }: FormFieldProps) => react_jsx_runtime.JSX.Element;
@@ -11,9 +11,21 @@ interface TextInputProps extends BaseInputProps {
11
11
  length?: 2 | 3 | 4 | 5;
12
12
  feedback?: FeedbackProps;
13
13
  }
14
+ interface SelectInputProps {
15
+ disabled?: boolean;
16
+ feedback?: FeedbackProps;
17
+ children?: React.JSX.Element[];
18
+ inputId?: string;
19
+ label?: string;
20
+ description?: string;
21
+ optional?: boolean;
22
+ error?: boolean;
23
+ attributes?: JSX.IntrinsicElements['select'];
24
+ }
14
25
  interface PasswordInputProps extends BaseInputProps {
15
26
  disabled?: boolean;
16
27
  feedback?: FeedbackProps;
28
+ forgotPasswordLink?: string;
17
29
  }
18
30
  interface CheckBoxProps extends BaseInputProps {
19
31
  inputId: string;
@@ -50,4 +62,4 @@ type ErrorSummaryProps = {
50
62
  }[];
51
63
  };
52
64
 
53
- export type { CheckBoxProps as C, ErrorSummaryProps as E, FormFieldsetProps as F, PasswordInputProps as P, RadioButtonProps as R, TextInputProps as T, FeedbackProps as a, FormFieldProps as b };
65
+ export type { CheckBoxProps as C, ErrorSummaryProps as E, FormFieldsetProps as F, PasswordInputProps as P, RadioButtonProps as R, SelectInputProps as S, TextInputProps as T, FeedbackProps as a, FormFieldProps as b };
package/esm/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export { ErrorSummary } from './Error-summary.js';
7
7
  export { PasswordInput } from './PasswordInput.js';
8
8
  export { TextInput } from './TextInput.js';
9
9
  import 'react/jsx-runtime';
10
- import './index-CufC9u5W.js';
10
+ import './index-B5zd_nrf.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@financial-times/o3-form",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Provides a viewport-aware tooltip for annotating or or highlighting other aspects of a product's UI",
5
5
  "keywords": [
6
6
  "form",
@@ -35,9 +35,11 @@
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@financial-times/o-utils": "^2.2.1",
38
- "@financial-times/o3-figma-sb-links": "^0.0.0",
39
38
  "@financial-times/o3-foundation": "^3.0.0"
40
39
  },
40
+ "devDependencies": {
41
+ "@financial-times/o3-figma-sb-links": "^0.0.0"
42
+ },
41
43
  "engines": {
42
44
  "npm": ">7"
43
45
  },