diff --git a/src/plugins/prime/stories/Form/Password/Password.mdx b/src/plugins/prime/stories/Form/Password/Password.mdx index 524de7c6..798e952e 100644 --- a/src/plugins/prime/stories/Form/Password/Password.mdx +++ b/src/plugins/prime/stories/Form/Password/Password.mdx @@ -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'; + - + -# 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} /> diff --git a/src/plugins/prime/stories/Form/Password/Password.stories.js b/src/plugins/prime/stories/Form/Password/Password.stories.js index 14eadc09..ce2260b8 100644 --- a/src/plugins/prime/stories/Form/Password/Password.stories.js +++ b/src/plugins/prime/stories/Form/Password/Password.stories.js @@ -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, + }, }; diff --git a/src/plugins/prime/stories/Form/Password/Password.template.js b/src/plugins/prime/stories/Form/Password/Password.template.js index 291ecb85..b2c03279 100644 --- a/src/plugins/prime/stories/Form/Password/Password.template.js +++ b/src/plugins/prime/stories/Form/Password/Password.template.js @@ -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> + `, }); diff --git a/src/plugins/prime/theme3.0/components/css/password.ts b/src/plugins/prime/theme3.0/components/css/password.ts new file mode 100644 index 00000000..bb296ce9 --- /dev/null +++ b/src/plugins/prime/theme3.0/components/css/password.ts @@ -0,0 +1,50 @@ +const css = ({ dt }: { dt: (token: string) => string }) => ` + + /* Иконки управления */ + .p-password-toggle-mask-icon, + .p-icon.p-password-toggle-mask-icon.p-password-unmask-icon { cursor: pointer; } + + /* Оверлей и индикатор */ + .p-password-overlay { + border-width: ${dt('password.extend.borderWidth')}; + } + + .p-password-meter-text { + color: ${dt('text.color')}; + font-size: ${dt('fonts.fontSize.200')}; + font-weight: ${dt('fonts.fontWeight.medium')}; + } + + /* Стили для кастомного контента */ + .p-password-rules { + display: flex; + flex-direction: column; + gap: ${dt('password.content.gap')}; + margin: 0; + padding: 0; + list-style: none; + } + + .p-password-rule { + display: flex; + align-items: center; + gap: ${dt('content.gap.200')}; + font-size: ${dt('fonts.fontSize.100')}; + } + + /* Состояния иконок */ + .p-password-rule i { + font-size: ${dt('fonts.fontSize.200')}; + } + + .p-password-rule .ti-circle { color: ${dt('surface.400')}; } + .p-password-rule .ti-circle-check { + color: ${dt('password.colorScheme.light.strength.strongBackground')}; + } + .p-password-rule .ti-circle-x { + color: ${dt('password.colorScheme.light.strength.weakBackground')}; + } + +`; + +export default css; diff --git a/src/plugins/prime/theme3.0/css.ts b/src/plugins/prime/theme3.0/css.ts index c68e950f..778e7f64 100644 --- a/src/plugins/prime/theme3.0/css.ts +++ b/src/plugins/prime/theme3.0/css.ts @@ -13,6 +13,7 @@ import dividerCss from './components/css/divider'; import drawerCss from './components/css/drawer'; import messageCss from './components/css/message'; import inputtextCss from './components/css/inputtext'; +import passwordCss from './components/css/password'; import popoverCss from './components/css/popover'; import progressbarCss from './components/css/progressbar'; import ratingCss from './components/css/rating'; @@ -40,6 +41,7 @@ const css = ({ dt }: { dt: (token: string) => string }) => ` ${drawerCss({ dt })} ${messageCss({ dt })} ${inputtextCss({ dt })} + ${passwordCss({ dt })} ${popoverCss({ dt })} ${progressbarCss({ dt })} ${popoverCss({ dt })}