File size: 3,507 Bytes
9697ede
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { type ComponentProps, type JSX, Show, splitProps } from "solid-js"
import { Icon } from "./icon"
import { useI18n } from "../../context/i18n"
import "./inline-input-v2.css"

export interface InlineInputV2Props extends Omit<ComponentProps<"input">, "type" | "prefix"> {
  /** Inline label shown before the field (prefix segment). */
  prefix: JSX.Element
  /** Fixed width for the prefix segment (px number or CSS length). Omit for fit-content. */
  labelWidth?: number | string
  /** Show the trailing copy action. */
  showCopyButton?: boolean
  /** Accessible label for the copy button. */
  copyLabel?: string
  onCopyClick?: (event: MouseEvent) => void
  /** Apply tabular numerals to the prefix and field value. */
  numeric?: boolean
  /** Error styling for the field and value text. */
  invalid?: boolean
  /** `base` is 28px tall; `large` is 32px tall. */
  appearance?: "base" | "large"
  type?: ComponentProps<"input">["type"]
}

export function InlineInputV2(props: InlineInputV2Props) {
  const i18n = useI18n()
  const [local, inputProps] = splitProps(props, [
    "class",
    "classList",
    "prefix",
    "labelWidth",
    "showCopyButton",
    "copyLabel",
    "onCopyClick",
    "numeric",
    "invalid",
    "appearance",
    "disabled",
    "style",
  ])

  let input: HTMLInputElement | undefined

  return (
    <div
      data-component="inline-input-v2"
      data-disabled={local.disabled ? "" : undefined}
      data-invalid={local.invalid ? "" : undefined}
      data-numeric={local.numeric ? "" : undefined}
      data-appearance={local.appearance ?? "base"}
      data-label-width={local.labelWidth != null ? "" : undefined}
      classList={{
        ...local.classList,
        [local.class ?? ""]: !!local.class,
      }}
      style={{
        ...(typeof local.style === "object" && local.style != null ? local.style : {}),
        ...(local.labelWidth != null
          ? {
              "--inline-input-v2-label-width":
                typeof local.labelWidth === "number" ? `${local.labelWidth}px` : local.labelWidth,
            }
          : {}),
      }}
    >
      <div
        data-slot="inline-input-v2-prefix"
        onMouseDown={(event) => {
          if (local.disabled || event.button !== 0) return
          // Keep focus on the input without using a native <label>, so external labels still work.
          event.preventDefault()
          input?.focus()
        }}
      >
        <span data-slot="inline-input-v2-prefix-text">{local.prefix}</span>
      </div>
      <div data-slot="inline-input-v2-divider" aria-hidden="true" />
      <div data-slot="inline-input-v2-field">
        <div data-slot="inline-input-v2-value">
          <input
            {...inputProps}
            ref={(el) => {
              input = el
              const ref = inputProps.ref
              if (typeof ref === "function") ref(el)
            }}
            type={inputProps.type ?? "text"}
            disabled={local.disabled}
            aria-invalid={local.invalid ? true : undefined}
            data-slot="inline-input-v2-input"
          />
        </div>
        <Show when={local.showCopyButton}>
          <button
            type="button"
            data-slot="inline-input-v2-icon-button"
            aria-label={local.copyLabel ?? i18n.t("ui.message.copy")}
            disabled={local.disabled}
            onClick={local.onCopyClick}
          >
            <Icon name="copy" />
          </button>
        </Show>
      </div>
    </div>
  )
}