fix: file upload
enhancement: widget styling
This commit is contained in:
@@ -16,6 +16,17 @@
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" integrity="sha512-dPXYcDub/aeb08c63jRq/k6GaKccl256JQy/AnOq7CAnEZ9FzSL9wSbcZkMp4R26vBsMLFYH4kQ67/bbV8XaCQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id="></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
gtag('config', 'G-');
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
||||
@@ -841,6 +841,11 @@ class Canvas extends React.Component {
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
const container = draggedElement.getAttribute("data-container")
|
||||
const canvasRect = this.canvasRef.current.getBoundingClientRect()
|
||||
|
||||
|
||||
@@ -645,6 +645,11 @@ class Widget extends React.Component {
|
||||
|
||||
handleDragEnter = (e, draggedElement, setOverElement) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
const dragEleType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
// console.log("Drag entering...", dragEleType, draggedElement, this.droppableTags)
|
||||
@@ -686,6 +691,12 @@ class Widget extends React.Component {
|
||||
}
|
||||
|
||||
handleDragOver = (e, draggedElement) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
if (draggedElement === this.elementRef.current) {
|
||||
// prevent drop on itself, since the widget is invisible when dragging, if dropped on itself, it may consume itself
|
||||
return
|
||||
@@ -706,8 +717,15 @@ class Widget extends React.Component {
|
||||
}
|
||||
|
||||
handleDropEvent = (e, draggedElement, widgetClass = null) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// FIXME: sometimes the elements showDroppableStyle is not gone, when dropping on the same widget
|
||||
this.setState({
|
||||
showDroppableStyle: {
|
||||
@@ -850,6 +868,8 @@ class Widget extends React.Component {
|
||||
data-draggable-type={this.getWidgetType()} // helps with droppable
|
||||
data-container={this.state.widgetContainer} // indicates how the canvas should handle dragging, one is sidebar other is canvas
|
||||
|
||||
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
|
||||
|
||||
draggable={this.state.dragEnabled}
|
||||
|
||||
onDragOver={(e) => this.handleDragOver(e, draggedElement)}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { useEffect, useMemo, useRef } from "react"
|
||||
import Draggable from "./utils/draggableDnd"
|
||||
|
||||
import { FileImageOutlined, GithubOutlined, GitlabOutlined, LinkOutlined,
|
||||
AudioOutlined, VideoCameraOutlined,
|
||||
FileTextOutlined} from "@ant-design/icons"
|
||||
import { GithubOutlined, GitlabOutlined, LinkOutlined,
|
||||
AudioOutlined, FileTextOutlined} from "@ant-design/icons"
|
||||
import DraggableWrapper from "./draggable/draggable"
|
||||
import { useDragContext } from "./draggable/draggableContext"
|
||||
|
||||
|
||||
export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){
|
||||
export function SidebarWidgetCard({ name, img, url, license, widgetClass, innerRef}){
|
||||
|
||||
const urlIcon = useMemo(() => {
|
||||
if (url){
|
||||
@@ -36,16 +34,37 @@ export function SidebarWidgetCard({ name, img, url, widgetClass, innerRef}){
|
||||
<div ref={innerRef} className="tw-select-none tw-h-[200px] tw-w-[230px] tw-flex tw-flex-col
|
||||
tw-rounded-md tw-overflow-hidden
|
||||
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
|
||||
tw-border-[#888] ">
|
||||
tw-border-blue-500 tw-shadow-md">
|
||||
<div className="tw-h-[200px] tw-pointer-events-none tw-w-full tw-overflow-hidden">
|
||||
<img src={img} alt={name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
|
||||
</div>
|
||||
<span className="tw-text-xl tw-text-center">{name}</span>
|
||||
<span className="tw-text-center tw-text-black tw-text-lg">{name}</span>
|
||||
<div className="tw-flex tw-text-lg tw-place tw-px-4">
|
||||
|
||||
<a href={url} className="tw-text-black" target="_blank" rel="noopener noreferrer">
|
||||
<a href={url} className="tw-text-gray-600" target="_blank" rel="noopener noreferrer">
|
||||
{urlIcon}
|
||||
</a>
|
||||
|
||||
{license?.name &&
|
||||
|
||||
<div className="tw-ml-auto tw-text-sm">
|
||||
{
|
||||
license.url ?
|
||||
<a href={license.url} target="_blank" rel="noreferrer noopener"
|
||||
className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
|
||||
tw-border-solid tw-rounded-sm tw-border-blue-500
|
||||
tw-shadow-md tw-text-center tw-no-underline">
|
||||
{license.name}
|
||||
</a>
|
||||
:
|
||||
<div className="tw-p-[1px] tw-px-2 tw-text-blue-500 tw-border-[1px]
|
||||
tw-border-solid tw-rounded-sm tw-border-blue-500
|
||||
tw-shadow-md tw-text-center">
|
||||
{license.name}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -87,9 +106,10 @@ export function DraggableAssetCard({file}){
|
||||
|
||||
|
||||
return (
|
||||
<Draggable className="tw-cursor-pointer">
|
||||
<>
|
||||
<div className="tw-w-full tw-h-[240px] tw-p-1 tw-flex tw-flex-col tw-rounded-md tw-overflow-hidden
|
||||
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px] tw-border-[#888] ">
|
||||
tw-gap-2 tw-text-gray-600 tw-bg-[#ffffff44] tw-border-solid tw-border-[1px]
|
||||
tw-border-blue-500 tw-shadow-md ">
|
||||
<div className="tw-h-[200px] tw-w-full tw-flex tw-place-content-center tw-p-1 tw-text-3xl tw-overflow-hidden">
|
||||
{ file.fileType === "image" &&
|
||||
<img src={file.previewUrl} alt={file.name} className="tw-object-contain tw-h-full tw-w-full tw-select-none" />
|
||||
@@ -113,9 +133,9 @@ export function DraggableAssetCard({file}){
|
||||
}
|
||||
|
||||
</div>
|
||||
<span className="tw-text-base">{file.name}</span>
|
||||
<span className="tw-text-sm tw-text-back">{file.name}</span>
|
||||
</div>
|
||||
</Draggable>
|
||||
</>
|
||||
)
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ const DraggableWrapper = memo(({dragElementType, dragWidgetClass=null, className
|
||||
return (
|
||||
<div className={`${className}`}
|
||||
draggable
|
||||
data-drag-start-within // this attribute indicates that the drag is occurring from within the project and not a outside file drop
|
||||
data-draggable-type={dragElementType}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
|
||||
@@ -18,6 +18,11 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
||||
|
||||
const handleDragEnter = (e) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
const dragElementType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
|
||||
@@ -42,6 +47,12 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
||||
}
|
||||
|
||||
const handleDragOver = (e) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
// console.log("Drag over: ", e.dataTransfer.getData("text/plain"), e.dataTransfer)
|
||||
const dragElementType = draggedElement.getAttribute("data-draggable-type")
|
||||
|
||||
@@ -57,6 +68,12 @@ const DroppableWrapper = memo(({onDrop, droppableTags={}, ...props}) => {
|
||||
}
|
||||
|
||||
const handleDropEvent = (e) => {
|
||||
|
||||
if (!draggedElement || !draggedElement.getAttribute("data-drag-start-within")){
|
||||
// if the drag is starting from outside (eg: file drop) or if drag doesn't exist
|
||||
return
|
||||
}
|
||||
|
||||
e.stopPropagation()
|
||||
|
||||
setShowDroppable({
|
||||
|
||||
@@ -1,6 +1,50 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import React, { useEffect, useState, useRef } from "react"
|
||||
import { Input, Button, Space, Radio } from "antd"
|
||||
import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons"
|
||||
import { SearchOutlined, CloseCircleFilled } from '@ant-design/icons'
|
||||
|
||||
|
||||
export const SearchComponent = ({ onSearch, searchValue, onClear, ...props }) => {
|
||||
const inputRef = useRef(null)
|
||||
|
||||
const handleOuterDivClick = () => {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="tw-flex tw-gap-2 input tw-place-items-center" onClick={handleOuterDivClick}>
|
||||
<SearchOutlined />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
className="tw-outline-none tw-w-full tw-border-none"
|
||||
id=""
|
||||
onInput={onSearch}
|
||||
value={searchValue}
|
||||
ref={inputRef} // Attach the ref to the input
|
||||
{...props}
|
||||
/>
|
||||
<div className="">
|
||||
{
|
||||
searchValue.length > 0 &&
|
||||
<div className="tw-cursor-pointer tw-text-gray-500"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if (onClear)
|
||||
onClear()
|
||||
}}>
|
||||
<CloseCircleFilled />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const DynamicInputList = () => {
|
||||
|
||||
@@ -9,33 +9,50 @@ import VideoPlayer from "./plugins/videoPlayer"
|
||||
import MapView from "./plugins/mapview"
|
||||
import PandasTable from "./plugins/pandasTable"
|
||||
|
||||
// TODO: add license for 3rd party plugins
|
||||
|
||||
const TkinterPluginWidgets = [
|
||||
{
|
||||
name: "Analog TimePicker",
|
||||
img: ClockImage,
|
||||
link: "https://github.com",
|
||||
widgetClass: AnalogTimePicker
|
||||
link: "https://github.com/PaulleDemon/tkTimePicker",
|
||||
widgetClass: AnalogTimePicker,
|
||||
license: {
|
||||
name: "MIT",
|
||||
url: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Video Player",
|
||||
img: VideoImage,
|
||||
link: "https://github.com/PaulleDemon/tkVideoPlayer",
|
||||
widgetClass: VideoPlayer
|
||||
link: "https://github.com/PaulleDemon/tkVideoPlayer",
|
||||
widgetClass: VideoPlayer,
|
||||
license: {
|
||||
name: "MIT",
|
||||
url: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Map viewer",
|
||||
img: MapImage,
|
||||
link: "https://github.com/TomSchimansky/TkinterMapView",
|
||||
widgetClass: MapView
|
||||
link: "https://github.com/TomSchimansky/TkinterMapView",
|
||||
widgetClass: MapView,
|
||||
license: {
|
||||
name: "MIT",
|
||||
url: ""
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Pandas Table",
|
||||
img: DataTableImage,
|
||||
link: "https://github.com/dmnfarrell/pandastable",
|
||||
widgetClass: PandasTable
|
||||
link: "https://github.com/dmnfarrell/pandastable",
|
||||
widgetClass: PandasTable,
|
||||
license: {
|
||||
name: "GPL v3",
|
||||
url: "https://github.com/dmnfarrell/pandastable/blob/master/LICENSE"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import ButtonWidget from "../assets/widgets/button.png"
|
||||
|
||||
import { filterObjectListStartingWith } from "../utils/filter"
|
||||
import Widget from "../canvas/widgets/base"
|
||||
import { SearchComponent } from "../components/inputs"
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,20 +53,9 @@ function PluginsContainer({sidebarContent, onWidgetsUpdate}){
|
||||
return (
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
|
||||
|
||||
<div className="tw-flex tw-gap-2 input tw-place-items-center">
|
||||
<SearchOutlined />
|
||||
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
|
||||
id="" onInput={onSearch} value={searchValue}/>
|
||||
<div className="">
|
||||
{
|
||||
searchValue.length > 0 &&
|
||||
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
|
||||
<CloseCircleFilled />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
|
||||
<SearchComponent onSearch={onSearch} searchValue={searchValue}
|
||||
onClear={() => setSearchValue("")} />
|
||||
<div className="tw-flex tw-flex-col tw-place-items-center tw-gap-2 tw-h-full tw-p-1">
|
||||
|
||||
{
|
||||
widgetData.map((widget, index) => {
|
||||
@@ -74,6 +64,7 @@ function PluginsContainer({sidebarContent, onWidgetsUpdate}){
|
||||
name={widget.name}
|
||||
img={widget.img}
|
||||
url={widget.link}
|
||||
license={widget.license}
|
||||
widgetClass={widget.widgetClass}
|
||||
/>
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ function Sidebar({tabs}){
|
||||
{
|
||||
sidebarTabs.map((tab, index) => {
|
||||
return (
|
||||
<div className={`${activeTab === index ? "tw-text-blue-400 " : "tw-text-gray-600"} tw-cursor-pointer
|
||||
hover:tw-text-blue-400 tw-flex tw-flex-col tw-gap-2 tw-place-items-center`}
|
||||
<div className={`${activeTab === index ? "tw-text-blue-500" : "tw-text-gray-600"}
|
||||
tw-cursor-pointer hover:tw-text-blue-500 tw-flex tw-flex-col tw-gap-2 tw-place-items-center`}
|
||||
key={tab.name}
|
||||
onMouseEnter={() => {
|
||||
openSidebar()
|
||||
@@ -73,7 +73,7 @@ function Sidebar({tabs}){
|
||||
setActiveTab(index)
|
||||
}}
|
||||
>
|
||||
<div className="tw-bg-white tw-shadow-lg tw-p-2 tw-rounded-md">
|
||||
<div className={`${activeTab === index && "tw-border-solid tw-border-[1px] tw-border-blue-500"} tw-bg-white tw-shadow-lg tw-p-2 tw-rounded-md`}>
|
||||
{tab.icon}
|
||||
</div>
|
||||
<span className="tw-text-[12px] ">{tab.name}</span>
|
||||
|
||||
@@ -8,6 +8,7 @@ import { CloseCircleFilled, InboxOutlined, SearchOutlined } from "@ant-design/ic
|
||||
import { DraggableAssetCard } from "../components/cards.js"
|
||||
import { filterObjectListStartingWith } from "../utils/filter"
|
||||
import { getFileType } from "../utils/file.js"
|
||||
import { SearchComponent } from "../components/inputs.js"
|
||||
// import { update } from "../redux/assetSlice.js"
|
||||
|
||||
|
||||
@@ -67,29 +68,18 @@ function UploadsContainer({assets, onAssetUploadChange}) {
|
||||
|
||||
return (
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col"
|
||||
onDragEnter={() => { setDragEnter(true) }}
|
||||
onDragLeave={(e) => {
|
||||
// Ensure the drag leave is happening on the container and not on a child element
|
||||
if (e.currentTarget.contains(e.relatedTarget)) {
|
||||
return
|
||||
}
|
||||
setDragEnter(false)
|
||||
}}
|
||||
>
|
||||
onDragEnter={() => { setDragEnter(true) }}
|
||||
onDragLeave={(e) => {
|
||||
// Ensure the drag leave is happening on the container and not on a child element
|
||||
if (e.currentTarget.contains(e.relatedTarget)) {
|
||||
return
|
||||
}
|
||||
setDragEnter(false)
|
||||
}}
|
||||
>
|
||||
|
||||
<div className="tw-flex tw-gap-2 input tw-place-items-center">
|
||||
<SearchOutlined />
|
||||
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
|
||||
id="" onInput={onSearch} value={searchValue} />
|
||||
<div className="">
|
||||
{
|
||||
searchValue.length > 0 &&
|
||||
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
|
||||
<CloseCircleFilled />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<SearchComponent onSearch={onSearch} searchValue={searchValue}
|
||||
onClear={() => setSearchValue("")} />
|
||||
<div className="tw-flex tw-relative tw-flex-col tw-gap-2 tw-h-full tw-p-1 tw-pb-4">
|
||||
<Dragger className={`${dragEnter && "!tw-h-[80vh] !tw-opacity-100 !tw-bg-[#fafafa] tw-absolute tw-top-0 tw-z-10"} tw-w-full !tw-min-w-[250px]`}
|
||||
{...props}
|
||||
|
||||
@@ -8,6 +8,7 @@ import ButtonWidget from "../assets/widgets/button.png"
|
||||
|
||||
import { filterObjectListStartingWith } from "../utils/filter"
|
||||
import Widget from "../canvas/widgets/base"
|
||||
import { SearchComponent } from "../components/inputs"
|
||||
|
||||
|
||||
/**
|
||||
@@ -52,20 +53,9 @@ function WidgetsContainer({sidebarContent, onWidgetsUpdate}){
|
||||
return (
|
||||
<div className="tw-w-full tw-p-2 tw-gap-4 tw-flex tw-flex-col tw-overflow-x-hidden">
|
||||
|
||||
<div className="tw-flex tw-gap-2 input tw-place-items-center">
|
||||
<SearchOutlined />
|
||||
<input type="text" placeholder="Search" className="tw-outline-none tw-w-full tw-border-none"
|
||||
id="" onInput={onSearch} value={searchValue}/>
|
||||
<div className="">
|
||||
{
|
||||
searchValue.length > 0 &&
|
||||
<div className="tw-cursor-pointer tw-text-gray-600" onClick={() => setSearchValue("")}>
|
||||
<CloseCircleFilled />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-flex tw-flex-col tw-gap-2 tw-h-full tw-p-1">
|
||||
<SearchComponent onSearch={onSearch} searchValue={searchValue}
|
||||
onClear={() => setSearchValue("")} />
|
||||
<div className="tw-flex tw-flex-col tw-place-items-center tw-gap-2 tw-h-full tw-p-1">
|
||||
|
||||
{
|
||||
widgetData.map((widget, index) => {
|
||||
@@ -81,6 +71,7 @@ function WidgetsContainer({sidebarContent, onWidgetsUpdate}){
|
||||
})
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -11,30 +11,37 @@ body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.dots-bg{
|
||||
background-image: url("../assets/background/dots.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.stripes-bg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
--color: #E1E1E1;
|
||||
background-color: #F3F3F3;
|
||||
background-image: linear-gradient(0deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent),
|
||||
linear-gradient(90deg, transparent 24%, var(--color) 25%, var(--color) 26%, transparent 27%,transparent 74%, var(--color) 75%, var(--color) 76%, transparent 77%,transparent);
|
||||
background-size: 55px 55px;
|
||||
}
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f151;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #b8b8b8ce;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
/* ::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
} */
|
||||
|
||||
.input{
|
||||
border: 2px solid #e3e5e8;
|
||||
border: 1px solid #e3e5e8;
|
||||
padding: 2px 8px;
|
||||
min-height: 40px;
|
||||
border-radius: 10px;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
transition: border 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.input:active, .input:focus, .input:focus-within{
|
||||
border-color: #60a5fa;
|
||||
border-color: #3285ea;
|
||||
box-shadow: 0 0 5px #085db780;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user