Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 22 additions & 36 deletions src/plugins/prime/stories/Form/Password/Password.mdx
Original file line number Diff line number Diff line change
@@ -1,48 +1,34 @@
import { Meta, Story } from '@storybook/addon-docs/blocks';
import { Meta, Canvas, Title, Description, Controls } from '@storybook/addon-docs/blocks';
import * as PasswordStories from './Password.stories';

import { Template, TemplateCustom } from './Password.template';
<Meta of={PasswordStories} title="Prime/Form/Password" />

<Meta of={PasswordStories} />
<Title />

# Password
<Description />

[PrimeVue Password](https://primevue.org/password), [Макет](https://www.figma.com/design/4TYeki0MDLhfPGJstbIicf/UI-kit-PrimeFace-\(DS\)?node-id=484-5881\&node-type=section\&t=1Q8VTt6Y4LnVAfrv-0)
```javascript
import Password from 'primevue/password';
```

### Basic
## Варианты использования

```html dark
<Password v-model="value" placeholder="Password" :feedback="false" />
```
### Default

<Story of={PasswordStories.Primary} />
Базовое использование компонента пароля с переключением видимости и индикатором сложности. Все параметры можно настроить через панель свойств.

### With Meter
<Canvas of={PasswordStories.Default} />

```html dark
<Password v-model="value" placeholder="Password" />
```
<Controls of={PasswordStories.Default} />

<Story of={PasswordStories.Meter} />

### Custom Rules

```html dark
<!-- checkRules, rules и getColor - кастомные структуры и функции, это пример реализации -->
<Password placeholder="Password" v-model="password" @change="checkRules">
<template #footer>
<div>
<div v-for="rule in rules" :key="rule.label">
<i
class="ti"
:class="rule.icon"
:style="{ color: getColor(rule.icon) }"
/>
<span>{{ rule.label }}</span>
</div>
</div>
</template>
</Password>
```
### Float Label

Использование с плавающим лейблом (variant="in").

<Canvas of={PasswordStories.FloatLabel} />

### Custom Content

Пример расширенного контента оверлея согласно Figma. Используются слоты `#header` и `#footer` для отображения заголовка и списка правил сложности с динамической индикацией (Tabler Icons).

<Story of={PasswordStories.Custom} />
<Canvas of={PasswordStories.CustomContent} />
111 changes: 100 additions & 11 deletions src/plugins/prime/stories/Form/Password/Password.stories.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,110 @@
import { Template, TemplateCustom } from './Password.template';
import Password from 'primevue/password';
import {
BasicTemplate,
FloatLabelTemplate,
CustomContentTemplate,
} from './Password.template';

export default {
/**
* Компонент ввода пароля с индикатором сложности и переключением видимости.
*/
const meta = {
title: 'Prime/Form/Password',
component: Password,
tags: ['autodocs'],
argTypes: {
size: {
control: 'select',
options: ['small', 'medium', 'large', 'xlarge'],
description: 'Размер поля ввода',
table: {
category: 'Props',
defaultValue: { summary: 'medium' },
},
},
toggleMask: {
control: 'boolean',
description: 'Отображает иконку переключения видимости пароля',
table: { category: 'Props', defaultValue: { summary: 'false' } },
},
feedback: {
control: 'boolean',
description: 'Отображает панель индикатора сложности при фокусе',
table: { category: 'Props', defaultValue: { summary: 'true' } },
},
promptLabel: {
control: 'text',
description: 'Текст заголовка панели подсказки',
table: { category: 'Props' },
},
weakLabel: {
control: 'text',
description: 'Текст для слабого пароля',
table: { category: 'Props' },
},
mediumLabel: {
control: 'text',
description: 'Текст для среднего пароля',
table: { category: 'Props' },
},
strongLabel: {
control: 'text',
description: 'Текст для сильного пароля',
table: { category: 'Props' },
},
invalid: {
control: 'boolean',
description: 'Флаг невалидности поля',
table: { category: 'Props' },
},
disabled: {
control: 'boolean',
description: 'Отключает возможность ввода',
table: { category: 'Props' },
},
placeholder: {
control: 'text',
description: 'Текст подсказки',
table: { category: 'Props' },
},
label: {
control: 'text',
description: 'Текст плавающего лейбла (для FloatLabel)',
table: { category: 'Custom' },
},
},
args: {
size: 'medium',
toggleMask: true,
feedback: true,
promptLabel: 'Выберите пароль',
weakLabel: 'Слишком простой',
mediumLabel: 'Средний уровень',
strongLabel: 'Надежный пароль',
invalid: false,
disabled: false,
placeholder: 'Введите сюда пароль...',
label: 'Пароль',
},
};

export const Primary = {
render: Template.bind({}),
export default meta;

args: {
feedback: false,
},
export const Default = {
render: BasicTemplate,
};

export const Meter = {
render: Template.bind({}),
export const FloatLabel = {
render: FloatLabelTemplate,
args: {
placeholder: undefined,
},
};

export const Custom = {
render: TemplateCustom.bind({}),
export const CustomContent = {
render: CustomContentTemplate,
args: {
feedback: true,
toggleMask: true,
},
};
165 changes: 72 additions & 93 deletions src/plugins/prime/stories/Form/Password/Password.template.js
Original file line number Diff line number Diff line change
@@ -1,110 +1,89 @@
import { ref } from 'vue';
import { ref, computed } from 'vue';
import Password from 'primevue/password';
import Divider from 'primevue/divider';
import FloatLabel from 'primevue/floatlabel';

export const Template = (args) => ({
export const BasicTemplate = (args) => ({
components: { Password },
setup() {
return { args };
const value = ref(null);
return { args, value };
},
template: `
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(4, max-content)', gap: '15px', alignItems: 'center', justifyItems: 'center' }">
<span></span>
<span></span>
<span><code>value="Password"</code></span>
<span><code>toggleMask</code></span>

<span :style="{ justifySelf: 'flex-start' }"></span>
<Password placeholder="Password" v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" toggleMask v-bind="args" />

<span :style="{ justifySelf: 'flex-start' }"><code>invalid</code></span>
<Password placeholder="Password" invalid v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" invalid v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" toggleMask invalid v-bind="args" />

<span :style="{ justifySelf: 'flex-start' }"><code>disabled</code></span>
<Password placeholder="Password" disabled v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" disabled v-bind="args" />
<Password placeholder="Password" :default-value="'Password'" toggleMask disabled v-bind="args" />
</div>
`,
<Password
v-model="value"
v-bind="args"
style="width: 100%"
inputStyle="width: 100%"
:class="{ 'p-inputtext-xlg': args.size === 'xlarge' }"
/>
`,
});

const PasswordCustom = {
components: { Password },
export const FloatLabelTemplate = (args) => ({
components: { Password, FloatLabel },
setup() {
const password = ref('');

const rules = ref([
{ label: 'At least 8 characters', regex: /.{8,}/, icon: 'ti-point' },
{
label: 'At least one lowercase letter',
regex: /[a-z]/,
icon: 'ti-point',
},
{
label: 'At least one uppercase letter',
regex: /[A-Z]/,
icon: 'ti-point',
},
{ label: 'At least one number', regex: /[0-9]/, icon: 'ti-point' },
{
label: 'At least one special character',
regex: /[!@#$%^&*]/,
icon: 'ti-point',
},
]);
const value = ref(null);
const inputProps = computed(() => {
const rest = { ...args };
delete rest.label;
return rest;
});

const checkRules = () => {
rules.value.forEach((rule) => {
rule.icon = rule.regex.test(password.value)
? 'ti-circle-check'
: 'ti-circle-x';
});
};

const getColor = (icon) => {
switch (icon) {
case 'ti-circle-check':
return 'green';
case 'ti-circle-x':
return 'red';
default:
return 'grey';
}
};

return { password, rules, checkRules, getColor };
return { args, value, inputProps };
},
template: `
<Password placeholder="Password" v-model="password" @change="checkRules">
<template #footer>
<div :style="{ padding: '1rem' }">
<div v-for="rule in rules" :key="rule.label" :style="{ display: 'flex', alignItems: 'center', gap: '0.5rem' }">
<i class="ti" :class="rule.icon" :style="{ color: getColor(rule.icon) }" />
<span>{{ rule.label }}</span>
</div>
</div>
</template>
</Password>
`,
};
<FloatLabel variant="in">
<Password
id="password_label"
v-model="value"
v-bind="inputProps"
variant="filled"
style="width: 100%"
inputStyle="width: 100%"
:class="{ 'p-inputtext-xlg': args.size === 'xlarge' }"
/>
<label for="password_label">{{ args.label || 'Password' }}</label>
</FloatLabel>
`,
});

export const TemplateCustom = (args) => ({
components: { PasswordCustom },
export const CustomContentTemplate = (args) => ({
components: { Password, Divider },
setup() {
return { args };
const value = ref(null);
return { args, value };
},
template: `
<div :style="{ display: 'grid', gridTemplateColumns: 'repeat(3, max-content)', gap: '15px', alignItems: 'center', justifyItems: 'center' }">
<span></span>
<span><code>value="Password"</code></span>
<span><code>toggleMask</code></span>

<PasswordCustom v-bind="args" />
<PasswordCustom :default-value="'Password'" v-bind="args" />
<PasswordCustom :default-value="'Password'" toggleMask v-bind="args" />
</div>
`,
<Password v-model="value" v-bind="args" style="width: 100%" inputStyle="width: 100%">
<template #header>
<div class="font-semibold text-sm mb-2" style="font-size: 0.875rem;">Введите пароль</div>
</template>
<template #footer>
<Divider />
<ul class="p-password-rules mt-2">
<li class="p-password-rule">
<i :class="['ti', !value ? 'ti-circle' : (value.length >= 8 ? 'ti-circle-check' : 'ti-circle-x')]" />
<span>Минимум 8 символов</span>
</li>
<li class="p-password-rule">
<i :class="['ti', !value ? 'ti-circle' : (/[a-z]/.test(value) ? 'ti-circle-check' : 'ti-circle-x')]" />
<span>Строчная буква</span>
</li>
<li class="p-password-rule">
<i :class="['ti', !value ? 'ti-circle' : (/[A-Z]/.test(value) ? 'ti-circle-check' : 'ti-circle-x')]" />
<span>Заглавная буква</span>
</li>
<li class="p-password-rule">
<i :class="['ti', !value ? 'ti-circle' : (/[0-9]/.test(value) ? 'ti-circle-check' : 'ti-circle-x')]" />
<span>Хотя бы одна цифра</span>
</li>
<li class="p-password-rule">
<i :class="['ti', !value ? 'ti-circle' : (/[^A-Za-z0-9]/.test(value) ? 'ti-circle-check' : 'ti-circle-x')]" />
<span>Спецсимвол</span>
</li>
</ul>
</template>
</Password>
`,
});
Loading
Loading