Add:Eye icon
This commit is contained in:
parent
b1aa414190
commit
959a7e3f85
18 changed files with 118 additions and 43 deletions
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
{"id":"dev","timestamp":1768275611480}
|
||||
{"id":"dev","timestamp":1768276905519}
|
||||
|
|
@ -1 +1 @@
|
|||
{"id":"dev","timestamp":1768275611480,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|
||||
{"id":"dev","timestamp":1768276905519,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"date": "2026-01-13T03:40:17.768Z",
|
||||
"date": "2026-01-13T04:01:52.330Z",
|
||||
"preset": "nitro-dev",
|
||||
"framework": {
|
||||
"name": "nuxt",
|
||||
|
|
@ -9,9 +9,9 @@
|
|||
"nitro": "2.12.8"
|
||||
},
|
||||
"dev": {
|
||||
"pid": 14040,
|
||||
"pid": 17152,
|
||||
"workerAddress": {
|
||||
"socketPath": "\\\\.\\pipe\\nitro-worker-14040-1-1-9354.sock"
|
||||
"socketPath": "\\\\.\\pipe\\nitro-worker-17152-1-1-4801.sock"
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Frontend-Learner/.nuxt/nuxt.d.ts
vendored
4
Frontend-Learner/.nuxt/nuxt.d.ts
vendored
|
|
@ -1,8 +1,8 @@
|
|||
/// <reference types="quasar" />
|
||||
/// <reference types="nuxt-quasar-ui" />
|
||||
/// <reference types="@nuxt/telemetry" />
|
||||
/// <reference types="@nuxtjs/tailwindcss" />
|
||||
/// <reference types="nuxt-quasar-ui" />
|
||||
/// <reference types="@nuxt/devtools" />
|
||||
/// <reference types="@nuxt/telemetry" />
|
||||
/// <reference path="types/builder-env.d.ts" />
|
||||
/// <reference types="nuxt" />
|
||||
/// <reference path="types/app-defaults.d.ts" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 13/1/2569 10:40:15
|
||||
// generated by the @nuxtjs/tailwindcss <https://github.com/nuxt-modules/tailwindcss> module at 13/1/2569 11:18:03
|
||||
import "@nuxtjs/tailwindcss/config-ctx"
|
||||
import configMerger from "@nuxtjs/tailwindcss/merger";
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
/**
|
||||
* @file FormInput.vue
|
||||
* @description Reusable input component with label, error handling, and support for disabled/required states.
|
||||
* Now supports password visibility toggle.
|
||||
*/
|
||||
|
||||
defineProps<{
|
||||
const props = defineProps<{
|
||||
modelValue: string
|
||||
label: string
|
||||
type?: string
|
||||
|
|
@ -19,6 +20,22 @@ const emit = defineEmits<{
|
|||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
// Password visibility state
|
||||
const showPassword = ref(false)
|
||||
|
||||
// Toggle function
|
||||
const togglePassword = () => {
|
||||
showPassword.value = !showPassword.value
|
||||
}
|
||||
|
||||
// Compute input type based on visibility state
|
||||
const inputType = computed(() => {
|
||||
if (props.type === 'password') {
|
||||
return showPassword.value ? 'text' : 'password'
|
||||
}
|
||||
return props.type || 'text'
|
||||
})
|
||||
|
||||
const updateValue = (event: Event) => {
|
||||
emit('update:modelValue', (event.target as HTMLInputElement).value)
|
||||
}
|
||||
|
|
@ -30,15 +47,42 @@ const updateValue = (event: Event) => {
|
|||
{{ label }}
|
||||
<span v-if="required" class="required-mark">*</span>
|
||||
</label>
|
||||
<input
|
||||
:type="type || 'text'"
|
||||
:value="modelValue"
|
||||
class="input-field"
|
||||
:class="{ 'input-error': error }"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="updateValue"
|
||||
>
|
||||
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
:type="inputType"
|
||||
:value="modelValue"
|
||||
class="input-field"
|
||||
:class="{ 'input-error': error, 'has-password-toggle': type === 'password' }"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="updateValue"
|
||||
>
|
||||
|
||||
<!-- Password Toggle Button -->
|
||||
<button
|
||||
v-if="type === 'password'"
|
||||
type="button"
|
||||
class="password-toggle-btn"
|
||||
@click="togglePassword"
|
||||
tabindex="-1"
|
||||
>
|
||||
<!-- Eye Icon (Show) -->
|
||||
<svg v-if="!showPassword" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
|
||||
<!-- Eye Off Icon (Hide) -->
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/>
|
||||
<path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/>
|
||||
<path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7c.44 0 .87-.03 1.28-.08"/>
|
||||
<line x1="2" y1="2" x2="22" y2="22"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span v-if="error" class="error-message">
|
||||
<span class="error-icon">⚠</span>
|
||||
{{ error }}
|
||||
|
|
@ -51,6 +95,37 @@ const updateValue = (event: Event) => {
|
|||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.password-toggle-btn {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #94a3b8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4px;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.password-toggle-btn:hover {
|
||||
color: #64748b;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.input-field.has-password-toggle {
|
||||
padding-right: 42px;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
color: var(--error);
|
||||
margin-left: 2px;
|
||||
|
|
|
|||
2
Frontend-Learner/node_modules/.cache/jiti/Frontend-Learner-nuxt.config.949dee75.mjs
generated
vendored
2
Frontend-Learner/node_modules/.cache/jiti/Frontend-Learner-nuxt.config.949dee75.mjs
generated
vendored
|
|
@ -40,4 +40,4 @@ var _default = exports.default = defineNuxtConfig({
|
|||
|
||||
}
|
||||
}
|
||||
}); /* v9-2bc4be59a7dd6932 */
|
||||
}); /* v9-1ad4b5e9404ba42a */
|
||||
|
|
|
|||
|
|
@ -32,4 +32,4 @@
|
|||
}
|
||||
},
|
||||
plugins: []
|
||||
}; /* v9-5906c7bca40137c7 */
|
||||
}; /* v9-24ec813999eb8913 */
|
||||
|
|
|
|||
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-core.js
generated
vendored
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-core.js
generated
vendored
|
|
@ -20,7 +20,7 @@ import {
|
|||
toggleClientConnected,
|
||||
updateDevToolsClientDetected,
|
||||
updateTimelineLayersState
|
||||
} from "./chunk-4EWULRPV.js";
|
||||
} from "./chunk-KUURTRQ3.js";
|
||||
|
||||
// node_modules/@vue/devtools-core/dist/index.js
|
||||
import { computed, inject, onUnmounted, ref, watch } from "vue";
|
||||
|
|
|
|||
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-core.js.map
generated
vendored
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-core.js.map
generated
vendored
File diff suppressed because one or more lines are too long
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-kit.js
generated
vendored
2
Frontend-Learner/node_modules/.cache/vite/client/deps/@vue_devtools-kit.js
generated
vendored
|
|
@ -81,7 +81,7 @@ import {
|
|||
updateDevToolsClientDetected,
|
||||
updateDevToolsState,
|
||||
updateTimelineLayersState
|
||||
} from "./chunk-4EWULRPV.js";
|
||||
} from "./chunk-KUURTRQ3.js";
|
||||
export {
|
||||
DevToolsContextHookKeys,
|
||||
DevToolsMessagingHookKeys,
|
||||
|
|
|
|||
18
Frontend-Learner/node_modules/.cache/vite/client/deps/_metadata.json
generated
vendored
18
Frontend-Learner/node_modules/.cache/vite/client/deps/_metadata.json
generated
vendored
|
|
@ -1,31 +1,31 @@
|
|||
{
|
||||
"hash": "725beae3",
|
||||
"configHash": "230b07ea",
|
||||
"lockfileHash": "189bf8ec",
|
||||
"browserHash": "922f090e",
|
||||
"hash": "06498dbb",
|
||||
"configHash": "5974d207",
|
||||
"lockfileHash": "a84e6c42",
|
||||
"browserHash": "63f18de1",
|
||||
"optimized": {
|
||||
"errx": {
|
||||
"src": "../../../../errx/dist/index.js",
|
||||
"file": "errx.js",
|
||||
"fileHash": "2195cf07",
|
||||
"fileHash": "222375d5",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@vue/devtools-core": {
|
||||
"src": "../../../../@vue/devtools-core/dist/index.js",
|
||||
"file": "@vue_devtools-core.js",
|
||||
"fileHash": "a62f2c91",
|
||||
"fileHash": "2c86f7fa",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@vue/devtools-kit": {
|
||||
"src": "../../../../@vue/devtools-kit/dist/index.js",
|
||||
"file": "@vue_devtools-kit.js",
|
||||
"fileHash": "cd5a5dd1",
|
||||
"fileHash": "cddf14f6",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
"chunks": {
|
||||
"chunk-4EWULRPV": {
|
||||
"file": "chunk-4EWULRPV.js"
|
||||
"chunk-KUURTRQ3": {
|
||||
"file": "chunk-KUURTRQ3.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Frontend-Learner/node_modules/.cache/vite/client/deps/chunk-4EWULRPV.js.map
generated
vendored
7
Frontend-Learner/node_modules/.cache/vite/client/deps/chunk-4EWULRPV.js.map
generated
vendored
File diff suppressed because one or more lines are too long
|
|
@ -5988,4 +5988,4 @@ export {
|
|||
parse,
|
||||
devtools
|
||||
};
|
||||
//# sourceMappingURL=chunk-4EWULRPV.js.map
|
||||
//# sourceMappingURL=chunk-KUURTRQ3.js.map
|
||||
7
Frontend-Learner/node_modules/.cache/vite/client/deps/chunk-KUURTRQ3.js.map
generated
vendored
Normal file
7
Frontend-Learner/node_modules/.cache/vite/client/deps/chunk-KUURTRQ3.js.map
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
2
Frontend-Learner/node_modules/.cache/vite/client/deps/errx.js.map
generated
vendored
2
Frontend-Learner/node_modules/.cache/vite/client/deps/errx.js.map
generated
vendored
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"version": 3,
|
||||
"sources": ["../../../../errx/dist/index.js"],
|
||||
"sourcesContent": ["const IS_ABSOLUTE_RE = /^[/\\\\](?![/\\\\])|^[/\\\\]{2}(?!\\.)|^[a-z]:[/\\\\]/i;\nconst LINE_RE = /^\\s+at (?:(?<function>[^)]+) \\()?(?<source>[^)]+)\\)?$/u;\nconst SOURCE_RE = /^(?<source>.+):(?<line>\\d+):(?<column>\\d+)$/u;\nfunction captureRawStackTrace() {\n if (!Error.captureStackTrace) {\n return;\n }\n const stack = new Error();\n Error.captureStackTrace(stack);\n return stack.stack;\n}\nfunction captureStackTrace() {\n const stack = captureRawStackTrace();\n return stack ? parseRawStackTrace(stack) : [];\n}\nfunction parseRawStackTrace(stacktrace) {\n const trace = [];\n for (const line of stacktrace.split(\"\\n\")) {\n const parsed = LINE_RE.exec(line)?.groups;\n if (!parsed) {\n continue;\n }\n if (!parsed.source) {\n continue;\n }\n const parsedSource = SOURCE_RE.exec(parsed.source)?.groups;\n if (parsedSource) {\n Object.assign(parsed, parsedSource);\n }\n if (IS_ABSOLUTE_RE.test(parsed.source)) {\n parsed.source = `file://${parsed.source}`;\n }\n if (parsed.source === import.meta.url) {\n continue;\n }\n for (const key of [\"line\", \"column\"]) {\n if (parsed[key]) {\n parsed[key] = Number(parsed[key]);\n }\n }\n trace.push(parsed);\n }\n return trace;\n}\n\nexport { captureRawStackTrace, captureStackTrace, parseRawStackTrace };\n"],
|
||||
"sourcesContent": ["const IS_ABSOLUTE_RE = /^[/\\\\](?![/\\\\])|^[/\\\\]{2}(?!\\.)|^[a-z]:[/\\\\]/i;\r\nconst LINE_RE = /^\\s+at (?:(?<function>[^)]+) \\()?(?<source>[^)]+)\\)?$/u;\r\nconst SOURCE_RE = /^(?<source>.+):(?<line>\\d+):(?<column>\\d+)$/u;\r\nfunction captureRawStackTrace() {\r\n if (!Error.captureStackTrace) {\r\n return;\r\n }\r\n const stack = new Error();\r\n Error.captureStackTrace(stack);\r\n return stack.stack;\r\n}\r\nfunction captureStackTrace() {\r\n const stack = captureRawStackTrace();\r\n return stack ? parseRawStackTrace(stack) : [];\r\n}\r\nfunction parseRawStackTrace(stacktrace) {\r\n const trace = [];\r\n for (const line of stacktrace.split(\"\\n\")) {\r\n const parsed = LINE_RE.exec(line)?.groups;\r\n if (!parsed) {\r\n continue;\r\n }\r\n if (!parsed.source) {\r\n continue;\r\n }\r\n const parsedSource = SOURCE_RE.exec(parsed.source)?.groups;\r\n if (parsedSource) {\r\n Object.assign(parsed, parsedSource);\r\n }\r\n if (IS_ABSOLUTE_RE.test(parsed.source)) {\r\n parsed.source = `file://${parsed.source}`;\r\n }\r\n if (parsed.source === import.meta.url) {\r\n continue;\r\n }\r\n for (const key of [\"line\", \"column\"]) {\r\n if (parsed[key]) {\r\n parsed[key] = Number(parsed[key]);\r\n }\r\n }\r\n trace.push(parsed);\r\n }\r\n return trace;\r\n}\r\n\r\nexport { captureRawStackTrace, captureStackTrace, parseRawStackTrace };\r\n"],
|
||||
"mappings": ";AAAA,IAAM,iBAAiB;AACvB,IAAM,UAAU;AAChB,IAAM,YAAY;AAClB,SAAS,uBAAuB;AAC9B,MAAI,CAAC,MAAM,mBAAmB;AAC5B;AAAA,EACF;AACA,QAAM,QAAQ,IAAI,MAAM;AACxB,QAAM,kBAAkB,KAAK;AAC7B,SAAO,MAAM;AACf;AACA,SAAS,oBAAoB;AAC3B,QAAM,QAAQ,qBAAqB;AACnC,SAAO,QAAQ,mBAAmB,KAAK,IAAI,CAAC;AAC9C;AACA,SAAS,mBAAmB,YAAY;AACtC,QAAM,QAAQ,CAAC;AACf,aAAW,QAAQ,WAAW,MAAM,IAAI,GAAG;AACzC,UAAM,SAAS,QAAQ,KAAK,IAAI,GAAG;AACnC,QAAI,CAAC,QAAQ;AACX;AAAA,IACF;AACA,QAAI,CAAC,OAAO,QAAQ;AAClB;AAAA,IACF;AACA,UAAM,eAAe,UAAU,KAAK,OAAO,MAAM,GAAG;AACpD,QAAI,cAAc;AAChB,aAAO,OAAO,QAAQ,YAAY;AAAA,IACpC;AACA,QAAI,eAAe,KAAK,OAAO,MAAM,GAAG;AACtC,aAAO,SAAS,UAAU,OAAO,MAAM;AAAA,IACzC;AACA,QAAI,OAAO,WAAW,YAAY,KAAK;AACrC;AAAA,IACF;AACA,eAAW,OAAO,CAAC,QAAQ,QAAQ,GAAG;AACpC,UAAI,OAAO,GAAG,GAAG;AACf,eAAO,GAAG,IAAI,OAAO,OAAO,GAAG,CAAC;AAAA,MAClC;AAAA,IACF;AACA,UAAM,KAAK,MAAM;AAAA,EACnB;AACA,SAAO;AACT;",
|
||||
"names": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ const handleRegister = async () => {
|
|||
v-model="registerForm.phone"
|
||||
label="เบอร์โทรศัพท์"
|
||||
type="tel"
|
||||
placeholder="0812345678"
|
||||
placeholder=""
|
||||
:error="errors.phone"
|
||||
required
|
||||
class="dark-form-input"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue