Field

Базовый компонент текстового поля. Basic text input component.

Import Permalink to "Import"

js
import ProximaField from 'proxima-vue/field';

Playground Permalink to "Playground"

Props
Label position
View
Round
Shadow
Actions visibility
js
import { ref } from 'vue';
import ProximaField from 'proxima-vue/field';

const value = ref('');
html
<ProximaField
  label="Username"
  placeholder="Enter username"
  v-model="value"
/>

Validity Permalink to "Validity"

Для демонстрации корректности заполнения поля можно использовать пропсы validityStyle и validityStatus, первый добавляет стили, второй иконку‑статус: To demonstrate the validity of a field, you can use the validityStyle and validityStatus props. The first adds styles, and the second adds a status icon:

html
<ProximaField
  label="Minlength 1"
  validity-style="both"
  validity-status="both"
/>

<ProximaField
  label="Minlength 6"
  validity-style="both"
  validity-status="both"
  :minlength="6"
/>

Можно указать одно из значений both, none, valid, invalid, последнее используется по умолчанию, для демонстрации текстовых ошибок необходимо использовать компонент в связке с формой. You can specify one of the values: both, none, valid or invalid. The last of these is used by default. To demonstrate text errors, you must use the component with the form.

Icon Permalink to "Icon"

Специальный слот позволяет добавить свою иконку внутрь поля: A special slot allows you to add your own icon inside the field:

html
<ProximaField
  placeholder="Type to search ..."
  v-model="value"
>
  <template #prepend>
    <ProximaIconSearch />
  </template>
</ProximaField>
js
import { ref } from 'vue';
import ProximaField from 'proxima-vue/field';
import ProximaIconSearch from 'proxima-vue/icon/search';

const value = ref('');

Actions Permalink to "Actions"

Вы можете добавить свои экшены через пропс actions: You can add your own actions using the actions prop:

html
<ProximaField
  placeholder="Field with voice action"
  actions-visibility="always"
  :actions="fieldActions"
/>
js
import { ref, unref, computed } from 'vue';
import ProximaField from 'proxima-vue/field';

const isRecording = ref(false);

const toggleRecording = () => {
  alert(unref(isRecording) ? 'stop recording' : 'start recording');
  isRecording.value = !unref(isRecording);
};

const fieldActions = computed(() => [
  {
    id: 'recording',
    pressed: unref(isRecording),
    html: '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" style="transform: scale(1.25);"><path fill="currentColor" d="M11 4v4c0 1.6-1.4 3-3 3S5 9.6 5 8V4c0-1.6 1.4-3 3-3s3 1.4 3 3Z"/><path fill="none" stroke-width="1" stroke="currentColor" d="M6 14.5h2v-2 2h2zM3.5 8c0 2.3 1.8 4.498 4.5 4.498S12.5 10.3 12.5 8"/></svg>',
    onClick: toggleRecording,
  },
]);
ts
interface ProximaFieldAction {
  id: string
  html: string
  shouldPreventBlur?: boolean
  pressed?: boolean
  onClick: Function
}

Skeleton Permalink to "Skeleton"

Режим плейсхолдера во время загрузки: Placeholder mode on loading:

Атрибут data‑ghost требует The attribute data‑ghost requires ghost.css

html
<div data-ghost="true" inert>
  <ProximaField
    label="Username"
  />
</div>

Accessibility Permalink to "Accessibility"

Компонент использует нативное поле input, для описания используется label, также можно добавить расширенное описание для вспомогательных технологий с помощью пропса describedby: The component uses a native input field, with a label used for description. You can also add an extended description for assistive technologies using the describedby prop:

html
<ProximaField
  describedby="my-hidden-description"
/>

<p id="my-hidden-description" hidden>
  The extended description for assistive technologies can be hidden
</p>

BEM Permalink to "BEM"

Блок компонента field, модификаторы (создаются автоматически на основе пропсов): Component block field, modifiers (automatically created based on props):

CSS Permalink to "CSS"

Слой компонента @layer proxima.field, также вы можете гибко cтилизовать компонент через кастомные свойства: Component layer @layer proxima.field, also you can style the component flexibly through custom properties:

css
--field-accent-color: var(--app-accent-color);
--field-background: var(--app-field-background);

--field-autofill-color: #e8f0fe;
--field-mask-color: var(--field-placeholder-color);
--field-transition: background-color 200ms, border-color 200ms;

/* Placeholder */
--field-placeholder-color: #6c757d;
--field-placeholder-font-family: inherit;
--field-placeholder-font-size: inherit;
--field-placeholder-font-weight: inherit;
--field-placeholder-font-style: normal;
--field-placeholder-line-height: normal;
--field-placeholder-opacity: 1;

/* Value */
--field-color: var(--app-field-color);
--field-caret-color: auto;
--field-font-family: inherit;
--field-font-weight: 400;
--field-font-style: normal;
--field-text-transform: none;
--field-text-align: start;
--field-line-height: normal;
--field-text-overflow: ellipsis;

/* Padding */
--field-padding-y: 0;
--field-padding-x-start: calc(
  (var(--field-size) * 0.25) +
  (var(--field-is-full-round) * 0.375rem)
);
--field-padding-x-end: calc(
  (var(--field-size) * 0.25) +
  (var(--field-is-full-round) * 0.375rem)
);

/* Max width */
--field-width-length-multiplier: calc(var(--field-font-size) * 0.61);
--field-max-width: calc(
  var(--field-padding-x-start) +
  var(--field-padding-x-end) + (
    var(--field-width-length) *
    var(--field-width-length-multiplier)
  )
);

/* Border */
--field-border-color: var(--app-field-border-color);
--field-border-width: 1px;
--field-border-style: solid;

/* Soft round */
--field-border-radius-divider: 16;
--field-border-radius: calc(
  var(--field-size) / var(--field-border-radius-divider)
);

/* Shadow */
--field-box-shadow-soft-size: 0.0625rem 0.0625rem 0.25rem;
--field-box-shadow-soft-color: rgba(0, 0, 0, 0.1);
--field-view-line-box-shadow-soft-size: 0;

/* Header */
--field-header-margin-y-end: calc(
  var(--field-label-font-size) * 0.375
);

/* Footer */
--field-footer-max-width: none;
--field-footer-margin-y-start: 0.5rem;

/* Label */
--field-label-letter-spacing: normal;
--field-label-line-height: 1.2;
--field-label-color: var(--app-label-color);
--field-label-required-display: inline;
--field-label-required-color: inherit;

--field-label-aside-header-justify-content: flex-start;
--field-label-aside-header-padding-x-end: 1rem;
--field-label-aside-width-multiplier: 10;
--field-label-aside-header-max-width: calc(
  var(--field-label-font-size) *
  var(--field-label-aside-width-multiplier)
);

--field-label-inside-padding-y-start: calc(var(--field-size) / 3.5);
--field-label-inside-label-transition: transform 200ms;
--field-label-inside-header-z-index: 5;
--field-label-inside-label-transform: scale(0.8) translateY(
  calc((33% + (var(--field-size) / 10)) * -1))
);

/* Prepend */
--field-prepend-font-size: calc(var(--field-font-size) * 1.25);
--field-prepend-width: var(--field-size);
--field-prepend-height: 100%;
--field-prepend-background: none;
--field-prepend-color: var(--field-color);
--field-prepend-padding-x: 0;
--field-prepend-padding-y: 0;
--field-prepend-border-style: var(--field-border-style);
--field-prepend-border-width: 0;
--field-prepend-border-color: var(--field-border-color);
--field-prepend-pointer-events: none;
--field-prepend-z-index: 2;

/* Action */
--field-action-color: #787a7d;
--field-action-pressed-color: var(--field-accent-color);
--field-action-size: var(--field-size);
--field-action-padding: 0;
--field-action-icon-stroke-width: revert-layer;
--field-action-icon-stroke-linecap: round;
--field-action-icon-stroke-linejoin: round;
--field-action-sep-display: block;
--field-action-sep-style: dotted;
--field-action-sep-width: 1px;
--field-action-sep-color: rgba(107, 107, 107, 0.5);
--field-action-transition: color 200ms, opacity 300ms;
--field-actions-z-index: 2;

/* Arrow */
--field-arrow-pointer-events: none;
--field-arrow-transition:
  transform 200ms cubic-bezier(.455, .03, .515, .955);

/* Status */
--field-status-size: var(--field-font-size);
--field-status-opacity: calc(1 - var(--field-is-hover-or-focus));
--field-status-background-display: block;
--field-status-background: linear-gradient(
  calc(90deg * var(--field-direction)),
  transparent,
  var(--field-background) 50%
);

--field-status-icon-stroke-width: 2;
--field-status-icon-stroke-linecap: round;
--field-status-icon-stroke-linejoin: round;

--field-status-invalid-color: var(--field-invalid-color);
--field-status-valid-color: var(--field-valid-color);
--field-status-z-index: 3;

--field-status-transform: translate3d(0, calc(
  var(--field-is-hover-or-focus) * -25%
), 0);

--field-status-transition:
  opacity 250ms ease-out,
  transform 250ms cubic-bezier(0, 0.55, 0.45, 1);

/* Invalid */
--field-invalid-color: var(--app-invalid-color);
--field-invalid-background: var(--field-background);
--field-invalid-border-color: var(--field-invalid-color);
--field-invalid-mask-color: color-mix(in srgb,
  var(--field-invalid-color), #fff 50%
);

/* Valid */
--field-valid-color: var(--app-valid-color);
--field-valid-background: var(--field-background);
--field-valid-border-color: var(--field-valid-color);

/* View line */
--field-view-line-padding-x: 0rem;
--field-view-line-background: transparent;
--field-view-line-border-radius: 0;
--field-view-line-highlight-offset: 0.25rem;

/* Disabled */
--field-disabled-opacity: var(--app-disabled-opacity);

/* Hover */
--field-hover-background: var(--field-background);
--field-hover-border-color: var(--field-accent-color);
--field-action-hover-color: var(--field-color);

/* Focus */
--field-focus-background: var(--field-background);
--field-focus-border-color: var(--field-accent-color);
--field-focus-placeholder-color: color-mix(in srgb,
  var(--field-placeholder-color), transparent 40%
);

/* Focus highlight */
--field-highlight-offset: var(--app-highlight-offset);
--field-highlight-style: var(--app-highlight-style);
--field-highlight-size: var(--app-highlight-size);
--field-highlight-color: var(--app-highlight-color);

/*
  Sizes
*/
--field-size-xxs: 1.875rem;
--field-font-size-xxs: 0.8125rem;
--field-label-font-size-xxs: 0.75rem;
--field-action-icon-size-xxs: 0.75rem;

--field-size-xs: 2.25rem;
--field-font-size-xs: 0.875rem;
--field-label-font-size-xs: 0.8125rem;
--field-action-icon-size-xs: 0.75rem;

--field-size-s: 2.5rem;
--field-font-size-s: 1rem;
--field-label-font-size-s: 0.875rem;
--field-action-icon-size-s: 0.875rem;

/* Normal */
--field-size: 2.75rem;
--field-font-size: 1rem;
--field-label-font-size: 0.875rem;
--field-action-icon-size: 0.875rem;

--field-size-m: 3rem;
--field-font-size-m: 1rem;
--field-label-font-size-m: 0.875rem;
--field-action-icon-size-m: 0.875rem;

--field-size-l: 3.375rem;
--field-font-size-l: 1.125rem;
--field-label-font-size-l: 1rem;
--field-action-icon-size-l: 1rem;

--field-size-xl: 3.75rem;
--field-font-size-xl: 1.25rem;
--field-label-font-size-xl: 1.125rem;
--field-action-icon-size-xl: 1.125rem;

--field-size-xxl: 4.125rem;
--field-font-size-xxl: 1.375rem;
--field-label-font-size-xxl: 1.25rem;
--field-action-icon-size-xxl: 1.125rem;

/*
  Flags (0 or 1, based on props)
*/

--field-direction: 1; /* or -1 on rtl */

--field-has-prepend: 0;
--field-has-actions: 0;
--field-has-label: 0;
--field-has-value: 0;
--field-has-status: 0;
--field-has-arrow: 0;

--field-is-valid: 0;
--field-is-invalid: 0;

--field-is-enabled: 0;
--field-is-disabled: 0;

--field-is-label-above: 0;
--field-is-label-aside: 0;
--field-is-label-inside: 0;

--field-is-actions-hover: 0;
--field-is-actions-always: 0;
--field-is-full-round: 0;
--field-is-arrow-flipped: 0;

--field-is-view-plain: 0;
--field-is-view-line: 0;

--field-is-hover: 0;
--field-is-focus: 0;

--field-is-hover-or-focus: max(
  var(--field-is-focus, 0),
  var(--field-is-hover, 0)
);
Locale Permalink to "Locale"

Компонент использует токены локализации: The component uses localization tokens:


js
{
  fieldInvalid: "The field is not valid",
  fieldRequired: "The field is required",
  fieldBadLength: "Minimum number of characters: {minlength}, currently: {count}",
}
Props Permalink to "Props"
ts
type ProximaFieldModelModifiers = {
  lazy?: boolean
  trim?: boolean
  number?: boolean
}

interface ProximaFieldProps {
  id?: string
  inputAttrs?: InputHTMLAttributes
  modelModifiers?: ProximaFieldModelModifiers
  modelValue?: string | number
  label?: string
  labelPosition?: 'above' | 'inside' | 'aside'
  describedby?: string
  autocomplete?: string
  placeholder?: string
  disabled?: boolean
  readonly?: boolean
  required?: boolean
  autofocus?: boolean
  minlength?: number
  maxlength?: number
  validityStyle?: 'none' | 'valid' | 'invalid' | 'both'
  validityStatus?: 'none' | 'valid' | 'invalid' | 'both'
  errorMessage?: string
  actions?: ProximaFieldAction[]
  actionsVisibility?: 'hover' | 'always'
  hasClearButton?: boolean
  hasArrowButton?: boolean
  width?: string
  validator?: (value: string, props?: ProximaFieldProps) => boolean
  emptyChecker?: (value: string) => boolean
  parseValue?: (value: any, currentValue: string) => string
  view?: 'plain' | 'line'
  size?: ProximaSize
  round?: 'soft' | 'full' | 'none'
  shadow?: 'soft' | 'none'
  theme?: string
}
Events Permalink to "Events"
html
<ProximaField
  @click:arrow="onFieldArrowClick"
  @update:modelValue="onFieldValueUpdate"
  @complete="onFieldComplete"
  @change="onFieldChange"
  @clear="onFieldClear"
  @focus="onFieldFocus"
  @blur="onFieldBlur"
/>
js
const onFieldArrowClick = () => {};
const onFieldValueUpdate = (modelValue) => {};
const onFieldComplete = (modelValue) => {};
const onFieldChange = (modelValue) => {};
const onFieldClear = (clearedValue) => {};
const onFieldFocus = (event) => {};
const onFieldBlur = (event) => {};
ts
type Emits = {
  'click:arrow': () => void
  'update:modelValue': (modelValue: string | number) => void
  'complete': (modelValue: string | number) => void
  'change': (modelValue: string | number) => void
  'clear': (clearedValue: string) => void
  'focus': (event: FocusEvent) => void
  'blur': (event: FocusEvent) => void
}
Slots Permalink to "Slots"
html
<ProximaField>
  <template #label="slotProps">
    you code (replaced label)
  </template>

  <template #header="slotProps">
    you code (append to header)
  </template>

  <template #prepend="slotProps">
    you code (append to prepend)
  </template>

  <template #field="slotProps">
    you code (replaced field)
  </template>

  <template #status-valid="slotProps">
    you code (replaced valid status)
  </template>

  <template #status-invalid="slotProps">
    you code (replaced invalid status)
  </template>

  <template #content="slotProps">
    you code (append to content)
  </template>

  <template #footer="slotProps">
    you code (append to footer)
  </template>
</ProximaField>
ts
type SlotProps = {
  id: string
  label: string
  placeholder: string
  fieldValue: string
  fieldAttrs: InputHTMLAttributes
  isFocused: boolean
  isCorrectLength: boolean
  isEmpty: boolean
  isValid: boolean
  hasLabel: boolean
  hasHeader: boolean
  hasFooter: boolean
  hasPrepend: boolean
  hasActions: boolean
  hasValidStyle: boolean
  hasInvalidStyle: boolean
  hasValidStatus: boolean
  hasInvalidStatus: boolean
  updateByEvent: () => void
  onBlur: () => void
  onFocus: () => void
  select: () => void
  clear: () => void
  focus: () => void
  blur: () => void
}
Expose Permalink to "Expose"
html
<ProximaField ref="field" />
ts
import { ref, unref, onMounted } from 'vue';
import ProximaField from 'proxima-vue/field';

const field = ref({} as InstanceType<typeof ProximaField>);

onMounted(() => {
  unref(field).checkFocus(); // false
  unref(field).focus();
  unref(field).checkFocus(); // true
});
ts
type ProximaFieldInstance = {
  getContainer: () => HTMLDivElement | null
  getElement: () => HTMLInputElement | null
  getLengthRange: () => [number, number]
  getValue: () => string
  getError: () => string
  getId: () => string
  checkEmpty: () => boolean
  checkFocus: () => boolean
  checkLength: () => boolean
  checkValidity: () => boolean
  select: () => void
  clear: () => void
  focus: () => void
  blur: () => void
}
View source on GitHub