began with settings page and with the input sanity check + hint
This commit is contained in:
parent
37f134fd13
commit
3c5354afb9
@ -1,42 +1,46 @@
|
||||
const server = 'http://192.168.4.22'
|
||||
const server = 'http://192.168.20.1'
|
||||
const timeout = 1500;
|
||||
async function restoreSession(reducer = null) {
|
||||
|
||||
let token = localStorage.getItem('token');
|
||||
let username = localStorage.getItem('username');
|
||||
console.log({ type: 'start', token, username });
|
||||
let active = token !== null && username !== null && await checkAuth(token);
|
||||
if (active && reducer !== null) {
|
||||
reducer({ type: 'start', token, username });
|
||||
console.log({ type: 'start', token, username });
|
||||
}
|
||||
return (active ? { active: true, token, username } : { active: false });
|
||||
}
|
||||
async function storeSession(username, token) {
|
||||
token = await token;
|
||||
localStorage.setItem('token', token);
|
||||
localStorage.setItem('username', username);
|
||||
return token
|
||||
async function storeSession(result, token) {
|
||||
result.token = await token;
|
||||
result.type = 'start';
|
||||
console.log(result)
|
||||
if (result.permanent) {
|
||||
localStorage.setItem('token', result.token);
|
||||
localStorage.setItem('username', result.username);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async function login(username, password) {
|
||||
async function login(formdata) {
|
||||
let formData = new FormData();
|
||||
formData.append('username', username)
|
||||
formData.append('password', password);
|
||||
formData.append('username', formdata.username)
|
||||
formData.append('password', formdata.password);
|
||||
formData.append('action', 'login');
|
||||
let result = { username: formdata.username, permanent: formdata.permanent }
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), 1500);
|
||||
const resp = fetch(`${server}/api/auth`, { signal: controller.signal, method: 'POST', mode: 'cors', body: formData })
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const resp = await fetch(`${server}/api/auth`, { signal: controller.signal, method: 'POST', mode: 'cors', body: formData })
|
||||
.then(resp => {
|
||||
if (resp.ok) return storeSession(username, resp.text())
|
||||
else if(resp.status===401) return 'login_failed';
|
||||
if (resp.ok) return storeSession(result, resp.text());
|
||||
else if (resp.status === 401)
|
||||
result.error = 'login_failed';
|
||||
else throw new Error(resp.error);
|
||||
return result;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error is:', error);
|
||||
return 'network_error';
|
||||
result.error = 'network_connection';
|
||||
})
|
||||
;
|
||||
return resp;
|
||||
return result;
|
||||
}
|
||||
async function logout(token) {
|
||||
let formData = new FormData()
|
||||
@ -54,10 +58,6 @@ async function checkAuth(token) {
|
||||
return await resp;
|
||||
}
|
||||
async function fetchdb(token = 'azif7eqCl5') {
|
||||
//fetch(`${server}/api/userdb`).then()
|
||||
//let xmlHttp = new XMLHttpRequest();
|
||||
//xmlHttp.open( "GET", `${server}/api/userdb`, false ); // false for synchronous request
|
||||
//xmlHttp.send( null );
|
||||
const resp = await fetch(`${server}/api/userdb`, { method: 'GET', mode: 'cors', headers: { Authentification: token } })
|
||||
.then(resp => resp.text()).then(text => parsedb(text))
|
||||
return resp;
|
||||
@ -105,6 +105,17 @@ async function catchRFID(token){
|
||||
.then(resp => resp.json());
|
||||
return resp;
|
||||
}
|
||||
const publicfunctions = { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser, restoreSession, catchRFID };
|
||||
|
||||
async function config(token, payload) {
|
||||
let reqdata = new FormData();
|
||||
for (let [k, v] of Object.entries(payload))
|
||||
if (['number', 'string'].includes(typeof (v)))
|
||||
reqdata.append(k, v);
|
||||
const abort = new AbortController();
|
||||
const aborttimer = setTimeout(() => abort.abort(), timeout);
|
||||
return fetch(`${server}/api/config`, { signal: abort.signal, method: 'POST', mode: 'cors', body: reqdata, headers:{Authentification: token} }).
|
||||
then(resp => resp.ok ? resp.json() : { error: resp.status });
|
||||
}
|
||||
const publicfunctions = { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser, restoreSession, catchRFID, config };
|
||||
export default { ...publicfunctions }
|
||||
export { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser, restoreSession, catchRFID }
|
||||
export { login, logout, checkAuth, parsedb, fetchdb, createCsvTable, updateUser, deleteUser, createUser, restoreSession, catchRFID, config }
|
@ -1,53 +1,54 @@
|
||||
import { h } from "preact";
|
||||
import { Router, route } from "preact-router";
|
||||
import { useEffect, useReducer } from "preact/hooks";
|
||||
import { useEffect, useReducer, useState } from "preact/hooks";
|
||||
import { Header, Menu } from "./index.js";
|
||||
import { Home, Users, EditUser, Login, Logout, System } from "../route";
|
||||
import { AppStateProvider, UserTableProvider, menuReducer, sessionReducer, userTableReducer } from "../store";
|
||||
import api from "../api/index.js";
|
||||
function App() {
|
||||
// useReducer
|
||||
const menu = useReducer(menuReducer, false);
|
||||
const session = useReducer(sessionReducer, {});
|
||||
const [usertable, userreducer] = useReducer(userTableReducer, []);
|
||||
console.log(session[0]);
|
||||
if(!session[0]||(session[0]&&session[0].active===undefined))
|
||||
api.restoreSession(session[1]);
|
||||
|
||||
useEffect(()=>{
|
||||
if(session[0]&&session[0].active){
|
||||
console.log("triggerimport");
|
||||
api.fetchdb(session[0].token).then(imported => {
|
||||
let action = { type: 'import', imported };
|
||||
userreducer(action);
|
||||
});
|
||||
}},[session]);
|
||||
|
||||
console.log(session);
|
||||
this.menu_items = [
|
||||
const menu_items = [
|
||||
{ text: "Übersicht", path: "/" },
|
||||
{ text: "Benutzer anlegen", path: "/newuser" },
|
||||
{ text: "Benutzer verwalten", path: "/users" },
|
||||
{ text: "System", path: "/system" },
|
||||
{ text: "Abmelden", path: "/logout" }
|
||||
]
|
||||
this.handleRoute = async e => {
|
||||
|
||||
function App() {
|
||||
// useReducer
|
||||
const menu = useReducer(menuReducer, false);
|
||||
const session = useReducer(sessionReducer, {});
|
||||
const [usertable, userreducer] = useReducer(userTableReducer, []);
|
||||
const [lasturl, setlasturl] = useState("/");
|
||||
if(!session[0]||(session[0]&&session[0].active===undefined))
|
||||
api.restoreSession(session[1]);
|
||||
useEffect(()=>{
|
||||
if(session[0]&&session[0].active){
|
||||
api.fetchdb(session[0].token).then(imported => {
|
||||
let action = { type: 'import', imported };
|
||||
userreducer(action);
|
||||
});
|
||||
}},[session]);
|
||||
const handleRoute = async e => {
|
||||
let active = session[0].active;
|
||||
|
||||
switch (e.url) {
|
||||
default:
|
||||
if (!session[0].active) route('/login', true);
|
||||
if (!active) route('/login', true);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
<AppStateProvider value={{ menu, session }} >
|
||||
<div id="wrapper">
|
||||
<Header title={"Doorlock"} />
|
||||
<div class="page">
|
||||
<Menu items={this.menu_items} />
|
||||
<Menu items={menu_items} />
|
||||
{!menu[0] &&
|
||||
<UserTableProvider value={{ usertable, userreducer }} >
|
||||
<Router onChange={this.handleRoute}>
|
||||
<Router onChange={handleRoute}>
|
||||
<Home path="/" user="me" />
|
||||
<Login path="login" />
|
||||
<System path="/system" />
|
||||
|
@ -1,11 +1,26 @@
|
||||
import { h } from 'preact'
|
||||
import { h, render } from 'preact'
|
||||
|
||||
function TextBox({ id, type = 'text', label, disabled, formdata, formchange, maxlength, overridevalue }) {
|
||||
let onChange = (e) => formchange({ ...formdata, [e.target.id]: e.target.value });
|
||||
return (<div className={'textbox'} >
|
||||
<input placeholder=" " disabled={disabled} type={type} value={overridevalue?overridevalue:formdata[id]?formdata[id]:''} id={id} onInput={onChange} maxlength={maxlength} name={`form-${id}`} />
|
||||
function TextBox({ id, type = 'text', label, disabled, formdata, formchange, maxlength, overridevalue, onChange, onInput, checkInput, allowdChars, showavailable,hinttext }) {
|
||||
|
||||
if (typeof (onInput) === 'undefined' && typeof (formchange) === 'function') {
|
||||
onInput = (e) => formchange(p => {
|
||||
if (typeof (checkInput) === 'function' && !checkInput(e.target.value)) {
|
||||
return ({ ...p, error: { [e.target.id]: 'input_error' } })
|
||||
}
|
||||
return ({ ...p, [e.target.id]: e.target.value, error: { [e.target.id]: undefined } })
|
||||
});
|
||||
}
|
||||
const onBlur = (e)=>{
|
||||
formchange(p =>({ ...p, error: { [e.target.id]: undefined } }));
|
||||
}
|
||||
return (<>
|
||||
<div className={'textbox'} >
|
||||
<input placeholder=" " disabled={disabled} type={type} value={overridevalue ? overridevalue : formdata[id] ? formdata[id] : ''} id={id} onBlur={onBlur} onInput={onInput} onChange={onChange} maxlength={maxlength} name={`form-${id}`} />
|
||||
<label for={id}>{label}</label>
|
||||
</div>)
|
||||
{formdata&&formdata.error&&formdata.error[id]&&hinttext&&<div className='textbox__hint'>{hinttext}</div>}
|
||||
</div>
|
||||
{showavailable && <span class="textbox__info"> {formdata[id] ? formdata[id].length : 0} von {maxlength} Zeichen verwendet.</span>}
|
||||
</>)
|
||||
}
|
||||
function CheckBox({ id, disabled, label, formdata, formchange }) {
|
||||
let onChange = (e) => formchange({ ...formdata, [e.target.id]: e.target.checked });
|
||||
|
@ -82,8 +82,7 @@ function EditUser({ userid }) {
|
||||
<h3>Eindeutige Identifikationsnummer</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={formchange} id="uid" label="Benutzer-ID" maxlength={maxlength_uid} />
|
||||
{<span class="textbox__info"> {formdata["uid"] ? formdata["uid"].length : 0} von {maxlength_uid} Zeichen verwendet.</span>}
|
||||
<TextBox info={true} formdata={formdata} formchange={formchange} id="uid" label="Benutzer-ID" maxlength={maxlength_uid} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<b>Info</b>
|
||||
@ -94,19 +93,16 @@ function EditUser({ userid }) {
|
||||
<h3>Persönliche Daten</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="first_name" label="Vorname" maxlength={maxlength_name} />
|
||||
{<span class="textbox__info"> {formdata["first_name"] ? formdata["first_name"].length : 0} von {maxlength_name} Zeichen verwendet.</span>}
|
||||
<TextBox formdata={formdata} formchange={formchange} id="first_name" label="Vorname" maxlength={maxlength_name} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="last_name" label="NachName" maxlength={maxlength_name} />
|
||||
{<span class="textbox__info"> {formdata["last_name"] ? formdata["last_name"].length : 0} von {maxlength_name} Zeichen verwendet.</span>}
|
||||
<TextBox formdata={formdata} formchange={formchange} id="last_name" label="NachName" maxlength={maxlength_name} showavailable />
|
||||
</div>
|
||||
</div>
|
||||
<h3>Authentifizierung</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="rfid_uid" label="RFID" maxlength={maxlength_rfid} disabled={rfidscan.active} overridevalue={rfidscan.active&&"Scannen..."}/>
|
||||
{<span class="textbox__info"> {formdata["rfid_uid"] ? formdata["rfid_uid"].length : 0} von {maxlength_rfid} Zeichen verwendet.</span>}
|
||||
<TextBox formdata={formdata} formchange={formchange} id="rfid_uid" label="RFID" maxlength={maxlength_rfid} disabled={rfidscan.active} overridevalue={rfidscan.active&&"Scannen..."} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Button onClick={runScan}>{rfidscan.active ? "Abbrechen" : "Scannen"}</Button>
|
||||
@ -115,8 +111,7 @@ function EditUser({ userid }) {
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox formdata={formdata} formchange={formchange} id="user_pin" label="Pin" maxlength={maxlength_pin} />
|
||||
{<span class="textbox__info"> {formdata["user_pin"] ? formdata["user_pin"].length : 0} von {maxlength_pin} Zeichen verwendet.</span>}
|
||||
<TextBox formdata={formdata} formchange={formchange} id="user_pin" label="Pin" maxlength={maxlength_pin} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<b>Info</b>
|
||||
|
@ -7,29 +7,18 @@ import { CheckBox, Button, TextBox } from '../../components/controls';
|
||||
import api from '../../api'
|
||||
function Login() {
|
||||
let [sessiondata, setsession] = useContext(AppState).session;
|
||||
const [val, set] = useState({ username: '', password: '', error: null });
|
||||
const [fromdata, setform] = useState({ username: '', password: '', error: null });
|
||||
const navigation = ["Login"];
|
||||
if (sessiondata.active)
|
||||
route('/', true);
|
||||
function onSubmit(e) {
|
||||
e.preventDefault();
|
||||
api.login(val.username, val.password).then(result => {
|
||||
if(result == 'login_failed'){
|
||||
set(prev=>({ ...prev, error: "login_failed",password: '' }));
|
||||
}
|
||||
else if(result=='network_error')
|
||||
set(prev=>({ ...prev, error: "network_connection" }));
|
||||
api.login(fromdata).then(result => {
|
||||
if (result.error !== undefined)
|
||||
setform(result);
|
||||
else {
|
||||
console.log(typeof(result))
|
||||
let newsession = {
|
||||
type: 'start',
|
||||
username: val.username,
|
||||
token: result
|
||||
setsession(result);
|
||||
}
|
||||
setsession(newsession);
|
||||
set({ username: '', password: '' });
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@ -40,18 +29,18 @@ function Login() {
|
||||
<h2>Anmeldung</h2>
|
||||
<p >Bitte melden Sie sich mit ihren Nutzerdaten an.</p>
|
||||
|
||||
{val.error ==='login_failed' && <Warnbox title='Anmeldefehler'>Ungültige Anmeldedaten.<br />Bitte überprüfen Sie den eingebenen Benutzernamen und das Passwort.</Warnbox>}
|
||||
{val.error ==='network_connection' && <Warnbox title='Netwerkfehler'>Die Kommunikation mit dem Gerät ist zurzeit nicht möglich.<br />Bitte überprüfen Sie die Netzwerkverbindung.</Warnbox>}
|
||||
{fromdata.error === 'login_failed' && <Warnbox title='Anmeldefehler'>Ungültige Anmeldedaten.<br />Bitte überprüfen Sie den eingebenen Benutzernamen und das Passwort.</Warnbox>}
|
||||
{fromdata.error === 'network_connection' && <Warnbox title='Netwerkfehler'>Die Kommunikation mit dem Gerät ist zurzeit nicht möglich.<br />Bitte überprüfen Sie die Netzwerkverbindung.</Warnbox>}
|
||||
<form id="login_form" onSubmit={onSubmit} >
|
||||
<div className='row'>
|
||||
<TextBox maxlength={25} formdata={val} formchange={set} id="username" label="Benutzername" />
|
||||
<TextBox maxlength={25} formdata={fromdata} formchange={setform} id="username" label="Benutzername" />
|
||||
</div>
|
||||
<div className='row'>
|
||||
<TextBox maxlength={25} formdata={val} formchange={set} id="password" label="Passwort" type='password' />
|
||||
<TextBox maxlength={25} formdata={fromdata} formchange={setform} id="password" label="Passwort" type='password' />
|
||||
|
||||
</div>
|
||||
<div className='row'>
|
||||
<CheckBox id="permanent" formdata={val} formchange={set} label="Angemeldet bleiben?" />
|
||||
<CheckBox id="permanent" formdata={fromdata} formchange={setform} label="Angemeldet bleiben?" />
|
||||
|
||||
</div>
|
||||
<Button onClick={onSubmit}>Anmelden</Button>
|
||||
|
@ -1,48 +1,112 @@
|
||||
import { h } from 'preact';
|
||||
import { Breadcrumbs } from "../../components";
|
||||
|
||||
import { useContext, useEffect, useState } from 'preact/hooks';
|
||||
import { CheckBox, Button, TextBox } from '../../components/controls';
|
||||
import AppState from '../../store/AppState';
|
||||
import api from '../../api'
|
||||
const checkIP = (v)=>{
|
||||
let parts = v.split('.');
|
||||
if(parts.length>4) return false;
|
||||
for(let part of parts)
|
||||
if(isNaN(part)||part>255||part<0||part.length>3)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
function System() {
|
||||
|
||||
let [session,] = useContext(AppState).session;
|
||||
let [formdata, setform] = useState({});
|
||||
useEffect(() => {
|
||||
api.config(session.token, { action: "get" }).then(r => setform(p => ({ ...p, ...r })));
|
||||
}, [session])
|
||||
return (
|
||||
<div className='container'>
|
||||
<Breadcrumbs items={['Systemeinstellungen']} />
|
||||
<div className={'contentbox'} >
|
||||
<h2>System</h2>
|
||||
|
||||
<form>
|
||||
<h3>Geräteeinstellungen</h3>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="mode" label="Modus" checkInput={(v)=>!isNaN(v)&&v>=0&&v<=1} maxlength={1} showavailable hinttext="Nur Zahlen 0 bis 1" />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="fail_timeout" label="Login Timeout" checkInput={(v)=>!isNaN(v)&&v>=0&&v<=255} maxlength={3} showavailable hinttext="Nur Zahlen 0 bis 255" />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="hold_time" label="Haltezeit" checkInput={(v)=>!isNaN(v)&&v>=0&&v<=255} maxlength={3} showavailable hinttext="Nur Zahlen 0 bis 255"/>
|
||||
</div>
|
||||
</div>
|
||||
<h3>WiFi Setup</h3>
|
||||
<hr />
|
||||
<form>
|
||||
<div className={'textbox'} >
|
||||
<input type="text" placeholder='Wifi network name' />
|
||||
<label>SSID</label>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="SSID" label="WiFi Name" maxlength={31} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="PASS" label="WiFi Passwort" maxlength={31} showavailable />
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="ip" label="IP Adresse" maxlength={15} showavailable checkInput={checkIP} hinttext="Beispiel: 192.168.20.1" />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="subnet" label="Netzmaske" maxlength={15} showavailable checkInput={checkIP} hinttext="Beispiel: 255.255.255.0"/>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="gw" label="Gateway" maxlength={15} showavailable checkInput={checkIP} hinttext="Beispiel: 0.0.0.0"/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<Button onClick={() => null} >Speichern</Button>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Button onClick={() => null} >Neustart</Button>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Button onClick={() => null} >Zurücksetzen</Button>
|
||||
</div>
|
||||
<div className={'textbox'} >
|
||||
<input type="text" placeholder='Wifi network name' />
|
||||
<label>Password</label>
|
||||
</div>
|
||||
</form>
|
||||
<form>
|
||||
<h3>Admin User</h3>
|
||||
<hr />
|
||||
<form>
|
||||
<div className={'textbox'} >
|
||||
<input type="text" placeholder='Wifi network name' />
|
||||
<label>Username</label>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="ip" label="Name" maxlength={15} showavailable />
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="subnet" label="Passwort" maxlength={15} showavailable />
|
||||
</div>
|
||||
<div className='column'>
|
||||
<TextBox info={true} formdata={formdata} formchange={setform} id="subnet" label="Passwort wiederholen" maxlength={15} showavailable />
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='column'>
|
||||
<Button onClick={() => null} >Speichern</Button>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Button onClick={() => null} >Zurücksetzen</Button>
|
||||
</div>
|
||||
<div className={'textbox'} >
|
||||
<input type="text" placeholder='Wifi network name' />
|
||||
<label>Password</label>
|
||||
</div>
|
||||
</form>
|
||||
<h3>Datenbank Backup</h3><hr />
|
||||
<div>
|
||||
<h3>Datenbank Backup</h3>
|
||||
<div className='row'>
|
||||
<div className="column">
|
||||
<h4>Backup einspielen</h4>
|
||||
<form>
|
||||
<h3>Backup einspielen</h3>
|
||||
<input type={'file'} />
|
||||
<input type={'submit'} value={'Hochladen'} />
|
||||
</form>
|
||||
<h3>Backup herunterladen</h3>
|
||||
</div>
|
||||
<div className="column">
|
||||
<h4>Backup herunterladen</h4>
|
||||
<button>Download</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
@ -19,12 +19,13 @@
|
||||
button
|
||||
display: block
|
||||
background: none
|
||||
min-width: 100%
|
||||
width: 100%
|
||||
border: none
|
||||
border-radius: none
|
||||
font-size: 2em
|
||||
transition: all ease-in-out 100ms
|
||||
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
&::before
|
||||
z-index: -1
|
||||
border-radius: .2rem
|
||||
|
@ -4,16 +4,17 @@
|
||||
position: relative
|
||||
display: block
|
||||
margin: 0
|
||||
overflow: hidden
|
||||
|
||||
background: #fafafa
|
||||
border-radius: .3em
|
||||
border-bottom: 1px solid #ccc
|
||||
&:hover
|
||||
background: #fff
|
||||
input
|
||||
//overflow: hidden
|
||||
display: block
|
||||
background: none
|
||||
min-width: 100%
|
||||
width: 100%
|
||||
border-radius: none
|
||||
outline: none
|
||||
border: none
|
||||
@ -28,13 +29,12 @@
|
||||
font-size: .7em
|
||||
cursor: text
|
||||
transition: 250ms all
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
&::placeholder
|
||||
color: transparent
|
||||
|
||||
&:placeholder-shown + label
|
||||
text-overflow: ellipsis
|
||||
color: grey
|
||||
position: absolute
|
||||
padding: .7em
|
||||
@ -46,6 +46,17 @@
|
||||
&__info
|
||||
color: #ccc
|
||||
font-size: 0.7em
|
||||
&__hint
|
||||
position: absolute
|
||||
z-index: 1
|
||||
left: 0
|
||||
right: 0
|
||||
top: calc(100% - .6em)
|
||||
border-bottom-left-radius: .3em
|
||||
border-bottom-right-radius: .3em
|
||||
color: #fff
|
||||
font-size: .7em
|
||||
background: red
|
||||
|
||||
@mixin checkbox
|
||||
position: relative
|
||||
|
Loading…
x
Reference in New Issue
Block a user