@otfdashkit/ui 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +99 -0
- package/dist/index.js +302 -1926
- package/dist/index.mjs +234 -1869
- package/package.json +24 -5
- package/src/advanced/BulkActions/BulkActions.tsx +2 -2
- package/src/advanced/CommandBar/CommandBar.tsx +2 -2
- package/src/advanced/Filters/Filters.tsx +6 -6
- package/src/advanced/Kanban/Kanban.tsx +1 -1
- package/src/advanced/ToggleButton/ToggleButton.tsx +1 -1
- package/src/advanced/bulk-actions.tsx +1 -46
- package/src/advanced/command-bar.tsx +1 -115
- package/src/advanced/filters.tsx +1 -103
- package/src/advanced/kanban.tsx +1 -95
- package/src/advanced/toggle-button.tsx +1 -78
- package/src/blocks/ChatDetail/ChatDetail.tsx +4 -4
- package/src/blocks/FeedbackModal/FeedbackModal.tsx +3 -3
- package/src/blocks/FileCards/FileCards.tsx +2 -2
- package/src/blocks/FilesList/FilesList.tsx +3 -3
- package/src/blocks/IntegrationCard/IntegrationCard.tsx +3 -3
- package/src/blocks/InviteModal/InviteModal.tsx +7 -7
- package/src/blocks/ManageTagsModal/ManageTagsModal.tsx +4 -4
- package/src/blocks/MessagesCard/MessagesCard.tsx +3 -3
- package/src/blocks/MetricCard/MetricCard.tsx +3 -3
- package/src/blocks/NotificationSettings/NotificationSettings.tsx +4 -4
- package/src/blocks/OrganizationMenu/OrganizationMenu.tsx +30 -11
- package/src/blocks/RolesMenu/RolesMenu.tsx +4 -4
- package/src/blocks/SelectUsersModal/SelectUsersModal.tsx +5 -5
- package/src/blocks/SidebarLayoutDashboard/SidebarLayoutDashboard.tsx +5 -5
- package/src/blocks/SidebarLayoutGroups/SidebarLayoutGroups.tsx +2 -2
- package/src/blocks/SidebarLayoutMinimal/SidebarLayoutMinimal.tsx +3 -3
- package/src/blocks/SidebarLayoutSearch/SidebarLayoutSearch.tsx +3 -3
- package/src/blocks/SidebarLayoutUser/SidebarLayoutUser.tsx +4 -4
- package/src/blocks/SortableTaskList/SortableTaskList.tsx +3 -3
- package/src/blocks/StackedLayoutBranded/StackedLayoutBranded.tsx +4 -4
- package/src/blocks/StackedLayoutTabs/StackedLayoutTabs.tsx +4 -4
- package/src/blocks/TaskCard/TaskCard.tsx +4 -4
- package/src/blocks/UserMenu/UserMenu.tsx +3 -3
- package/src/blocks/WorkspaceMembers/WorkspaceMembers.tsx +5 -5
- package/src/blocks/chat-detail.tsx +1 -73
- package/src/blocks/feedback-modal.tsx +1 -68
- package/src/blocks/file-cards.tsx +1 -56
- package/src/blocks/files-list.tsx +1 -72
- package/src/blocks/integration-card.tsx +1 -50
- package/src/blocks/invite-modal.tsx +1 -69
- package/src/blocks/manage-tags-modal.tsx +1 -63
- package/src/blocks/messages-card.tsx +1 -59
- package/src/blocks/metric-card.tsx +1 -79
- package/src/blocks/notification-settings.tsx +1 -61
- package/src/blocks/organization-menu.tsx +1 -85
- package/src/blocks/roles-menu.tsx +1 -61
- package/src/blocks/select-users-modal.tsx +1 -64
- package/src/blocks/sidebar-layout-dashboard.tsx +1 -97
- package/src/blocks/sidebar-layout-groups.tsx +1 -79
- package/src/blocks/sidebar-layout-minimal.tsx +1 -73
- package/src/blocks/sidebar-layout-search.tsx +1 -79
- package/src/blocks/sidebar-layout-user.tsx +1 -82
- package/src/blocks/sortable-task-list.tsx +1 -70
- package/src/blocks/stacked-layout-branded.tsx +1 -64
- package/src/blocks/stacked-layout-tabs.tsx +1 -80
- package/src/blocks/task-card.tsx +1 -88
- package/src/blocks/user-menu.tsx +1 -62
- package/src/blocks/workspace-members.tsx +1 -61
- package/src/charts/sparkline.tsx +1 -1
- package/src/components/DataGrid/DataGrid.tsx +1 -1
- package/src/components/DataTable/DataTable.tsx +4 -4
- package/src/components/EmptyState/EmptyState.tsx +6 -22
- package/src/components/Persona/Persona.tsx +1 -1
- package/src/components/Stat/Stat.tsx +1 -1
- package/src/components/Stepper/Stepper.tsx +13 -2
- package/src/components/banner.tsx +1 -203
- package/src/components/breadcrumb.tsx +1 -58
- package/src/components/command-item.tsx +1 -36
- package/src/components/data-grid.tsx +1 -125
- package/src/components/data-table.tsx +1 -139
- package/src/components/empty-state.tsx +1 -31
- package/src/components/hotkeys.tsx +1 -31
- package/src/components/icon-badge.tsx +1 -35
- package/src/components/loading-overlay.tsx +1 -38
- package/src/components/persona.tsx +1 -38
- package/src/components/property-list.tsx +1 -29
- package/src/components/stat.tsx +1 -51
- package/src/components/stepper.tsx +1 -69
- package/src/components/structured-list.tsx +1 -51
- package/src/components/timeline.tsx +1 -69
- package/src/components/toaster.tsx +1 -42
- package/src/data/DataList.tsx +1 -1
- package/src/engagement/Tour/Tour.tsx +1 -1
- package/src/engagement/beacon.tsx +1 -50
- package/src/engagement/tour.tsx +1 -106
- package/src/forms/ArrayField/ArrayField.tsx +2 -2
- package/src/forms/AutoForm/AutoForm.tsx +9 -8
- package/src/forms/DatePicker/DatePicker.tsx +3 -3
- package/src/forms/FloatingLabelInput/FloatingLabelInput.tsx +104 -0
- package/src/forms/FloatingLabelInput/index.ts +1 -0
- package/src/forms/PasswordInput/PasswordInput.tsx +2 -2
- package/src/forms/SearchInput/SearchInput.tsx +2 -2
- package/src/forms/StepForm/StepForm.tsx +2 -2
- package/src/forms/array-field.tsx +1 -61
- package/src/forms/auto-form.tsx +1 -133
- package/src/forms/date-picker.tsx +1 -109
- package/src/forms/field.tsx +1 -62
- package/src/forms/file-upload.tsx +1 -101
- package/src/forms/floating-label-input.tsx +1 -0
- package/src/forms/form.tsx +1 -57
- package/src/forms/object-field.tsx +1 -53
- package/src/forms/password-input.tsx +1 -54
- package/src/forms/search-input.tsx +1 -64
- package/src/forms/step-form.tsx +1 -113
- package/src/index.ts +6 -3
- package/src/primitives/Button/Button.tsx +18 -0
- package/src/primitives/code-block.tsx +1 -1
- package/src/primitives/comparison.tsx +1 -1
- package/src/primitives/qr-code.tsx +5 -1
- package/src/themes/base.css +18 -0
|
@@ -1,64 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Search, X } from 'lucide-react'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
import { type InputProps } from '../primitives/input'
|
|
5
|
-
|
|
6
|
-
export interface SearchInputProps extends Omit<InputProps, 'onChange'> {
|
|
7
|
-
shortcut?: string
|
|
8
|
-
onClear?: () => void
|
|
9
|
-
onChange?: (value: string) => void
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
|
|
13
|
-
({ className, shortcut, onClear, onChange, value: valueProp, defaultValue, ...props }, ref) => {
|
|
14
|
-
const [internalValue, setInternalValue] = useState(defaultValue ?? '')
|
|
15
|
-
const isControlled = valueProp !== undefined
|
|
16
|
-
const value = isControlled ? String(valueProp) : String(internalValue)
|
|
17
|
-
|
|
18
|
-
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
19
|
-
if (!isControlled) setInternalValue(e.target.value)
|
|
20
|
-
onChange?.(e.target.value)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function handleClear() {
|
|
24
|
-
if (!isControlled) setInternalValue('')
|
|
25
|
-
onChange?.('')
|
|
26
|
-
onClear?.()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<div className="relative flex items-center">
|
|
31
|
-
<Search className="pointer-events-none absolute left-2.5 h-4 w-4 text-[hsl(var(--muted-foreground))]" />
|
|
32
|
-
<input
|
|
33
|
-
ref={ref}
|
|
34
|
-
value={value}
|
|
35
|
-
onChange={handleChange}
|
|
36
|
-
className={cn(
|
|
37
|
-
'flex h-9 w-full rounded-md border border-[hsl(var(--input))] bg-transparent pl-8 pr-8 text-sm shadow-sm transition-colors',
|
|
38
|
-
'placeholder:text-[hsl(var(--muted-foreground))]',
|
|
39
|
-
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[hsl(var(--ring))]',
|
|
40
|
-
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
41
|
-
className
|
|
42
|
-
)}
|
|
43
|
-
{...props}
|
|
44
|
-
/>
|
|
45
|
-
<div className="absolute right-2.5 flex items-center">
|
|
46
|
-
{value ? (
|
|
47
|
-
<button
|
|
48
|
-
type="button"
|
|
49
|
-
onClick={handleClear}
|
|
50
|
-
className="text-[hsl(var(--muted-foreground))] hover:text-[hsl(var(--foreground))] transition-colors"
|
|
51
|
-
>
|
|
52
|
-
<X className="h-4 w-4" />
|
|
53
|
-
</button>
|
|
54
|
-
) : shortcut ? (
|
|
55
|
-
<kbd className="pointer-events-none rounded border border-[hsl(var(--border))] px-1.5 py-0.5 text-[10px] text-[hsl(var(--muted-foreground))]">
|
|
56
|
-
{shortcut}
|
|
57
|
-
</kbd>
|
|
58
|
-
) : null}
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
)
|
|
64
|
-
SearchInput.displayName = 'SearchInput'
|
|
1
|
+
export * from './SearchInput'
|
package/src/forms/step-form.tsx
CHANGED
|
@@ -1,113 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Button } from '../primitives/button'
|
|
3
|
-
import { cn } from '../utils/cn'
|
|
4
|
-
|
|
5
|
-
interface StepFormContextValue {
|
|
6
|
-
currentStep: number
|
|
7
|
-
totalSteps: number
|
|
8
|
-
steps: string[]
|
|
9
|
-
next: () => void
|
|
10
|
-
prev: () => void
|
|
11
|
-
isFirst: boolean
|
|
12
|
-
isLast: boolean
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const StepFormContext = createContext<StepFormContextValue | null>(null)
|
|
16
|
-
|
|
17
|
-
function useStepForm() {
|
|
18
|
-
const ctx = useContext(StepFormContext)
|
|
19
|
-
if (!ctx) throw new Error('useStepForm must be used within StepForm')
|
|
20
|
-
return ctx
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface StepFormProps {
|
|
24
|
-
steps: string[]
|
|
25
|
-
onSubmit: () => void
|
|
26
|
-
children: React.ReactNode
|
|
27
|
-
className?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function StepForm({ steps, onSubmit, children, className }: StepFormProps) {
|
|
31
|
-
const [currentStep, setCurrentStep] = useState(0)
|
|
32
|
-
const totalSteps = steps.length
|
|
33
|
-
|
|
34
|
-
const ctx: StepFormContextValue = {
|
|
35
|
-
currentStep,
|
|
36
|
-
totalSteps,
|
|
37
|
-
steps,
|
|
38
|
-
next: () => setCurrentStep(s => Math.min(s + 1, totalSteps - 1)),
|
|
39
|
-
prev: () => setCurrentStep(s => Math.max(s - 1, 0)),
|
|
40
|
-
isFirst: currentStep === 0,
|
|
41
|
-
isLast: currentStep === totalSteps - 1,
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const childArray = React.Children.toArray(children)
|
|
45
|
-
const currentChild = childArray[currentStep]
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<StepFormContext.Provider value={ctx}>
|
|
49
|
-
<div className={cn('flex flex-col gap-6', className)}>
|
|
50
|
-
<StepIndicator steps={steps} currentStep={currentStep} />
|
|
51
|
-
<div>{currentChild}</div>
|
|
52
|
-
<StepFormNavigation onSubmit={onSubmit} />
|
|
53
|
-
</div>
|
|
54
|
-
</StepFormContext.Provider>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function StepIndicator({ steps, currentStep }: { steps: string[]; currentStep: number }) {
|
|
59
|
-
return (
|
|
60
|
-
<div className="flex items-center gap-2">
|
|
61
|
-
{steps.map((label, i) => (
|
|
62
|
-
<React.Fragment key={i}>
|
|
63
|
-
{i > 0 && <div className={cn('h-px flex-1 bg-[hsl(var(--border))]', i <= currentStep && 'bg-[hsl(var(--primary))]')} />}
|
|
64
|
-
<div className="flex flex-col items-center gap-1">
|
|
65
|
-
<div className={cn(
|
|
66
|
-
'flex h-7 w-7 items-center justify-center rounded-full text-xs font-medium border transition-colors',
|
|
67
|
-
i < currentStep && 'bg-[hsl(var(--primary))] border-[hsl(var(--primary))] text-[hsl(var(--primary-foreground))]',
|
|
68
|
-
i === currentStep && 'border-[hsl(var(--primary))] text-[hsl(var(--primary))]',
|
|
69
|
-
i > currentStep && 'border-[hsl(var(--border))] text-[hsl(var(--muted-foreground))]'
|
|
70
|
-
)}>{i + 1}</div>
|
|
71
|
-
<span className="hidden text-[10px] text-[hsl(var(--muted-foreground))] sm:block">{label}</span>
|
|
72
|
-
</div>
|
|
73
|
-
</React.Fragment>
|
|
74
|
-
))}
|
|
75
|
-
</div>
|
|
76
|
-
)
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export interface StepFormStepProps {
|
|
80
|
-
title: string
|
|
81
|
-
description?: string
|
|
82
|
-
children: React.ReactNode
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function StepFormStep({ title, description, children }: StepFormStepProps) {
|
|
86
|
-
return (
|
|
87
|
-
<div className="flex flex-col gap-4">
|
|
88
|
-
<div>
|
|
89
|
-
<h3 className="text-base font-semibold text-[hsl(var(--foreground))]">{title}</h3>
|
|
90
|
-
{description && <p className="text-sm text-[hsl(var(--muted-foreground))]">{description}</p>}
|
|
91
|
-
</div>
|
|
92
|
-
{children}
|
|
93
|
-
</div>
|
|
94
|
-
)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export interface StepFormNavigationProps {
|
|
98
|
-
onSubmit?: () => void
|
|
99
|
-
submitLabel?: string
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function StepFormNavigation({ onSubmit, submitLabel = 'Submit' }: StepFormNavigationProps) {
|
|
103
|
-
const { prev, next, isFirst, isLast } = useStepForm()
|
|
104
|
-
return (
|
|
105
|
-
<div className="flex justify-between">
|
|
106
|
-
<Button type="button" variant="outline" onClick={prev} disabled={isFirst}>Previous</Button>
|
|
107
|
-
{isLast
|
|
108
|
-
? <Button type="button" onClick={onSubmit}>{submitLabel}</Button>
|
|
109
|
-
: <Button type="button" onClick={next}>Next</Button>
|
|
110
|
-
}
|
|
111
|
-
</div>
|
|
112
|
-
)
|
|
113
|
-
}
|
|
1
|
+
export * from './StepForm'
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export * from './primitives/context-menu'
|
|
|
23
23
|
export * from './primitives/dialog'
|
|
24
24
|
export * from './primitives/drawer'
|
|
25
25
|
export * from './primitives/dropdown-menu'
|
|
26
|
-
export * from './primitives/form'
|
|
26
|
+
// export * from './primitives/form' // conflicts with forms/form.tsx (Form, FormField, etc.)
|
|
27
27
|
export * from './primitives/hover-card'
|
|
28
28
|
export * from './primitives/input-otp'
|
|
29
29
|
export * from './primitives/input'
|
|
@@ -59,7 +59,7 @@ export * from './primitives/tooltip'
|
|
|
59
59
|
// components
|
|
60
60
|
export * from './components/banner'
|
|
61
61
|
export * from './components/breadcrumb'
|
|
62
|
-
export
|
|
62
|
+
export { CommandListItem } from './components/command-item'
|
|
63
63
|
export * from './components/data-grid'
|
|
64
64
|
export * from './components/data-table'
|
|
65
65
|
export * from './components/empty-state'
|
|
@@ -132,6 +132,7 @@ export * from './forms/color-picker'
|
|
|
132
132
|
export * from './forms/date-picker'
|
|
133
133
|
export * from './forms/field'
|
|
134
134
|
export * from './forms/file-upload'
|
|
135
|
+
export * from './forms/floating-label-input'
|
|
135
136
|
export * from './forms/form'
|
|
136
137
|
export * from './forms/object-field'
|
|
137
138
|
export * from './forms/password-input'
|
|
@@ -153,7 +154,9 @@ export * from './layouts/stack'
|
|
|
153
154
|
export { PageLayout } from './layout/PageLayout'
|
|
154
155
|
export type { PageHeaderProps } from './layout/PageLayout'
|
|
155
156
|
export { PageContainer, PageMain } from './layout/PageContainer'
|
|
156
|
-
export
|
|
157
|
+
export type { CommandPaletteProps } from './nav/CommandPalette'
|
|
158
|
+
export { CommandPaletteProvider, useCommandPalette, CommandPalette } from './nav/CommandPalette'
|
|
159
|
+
export * from './nav/HotkeysProvider'
|
|
157
160
|
export * from './feedback'
|
|
158
161
|
export * from './text'
|
|
159
162
|
export * from './backgrounds'
|
|
@@ -43,6 +43,24 @@ const buttonVariants = cva(
|
|
|
43
43
|
'hover:border-border/60 hover:bg-accent',
|
|
44
44
|
'active:shadow-none',
|
|
45
45
|
].join(' '),
|
|
46
|
+
/** Shimmer — animated conic gradient sweep with a hover highlight.
|
|
47
|
+
* Loud + premium; reserve for primary CTAs and pricing pages, not
|
|
48
|
+
* for in-product UI (gets tiring fast). Cross-platform parity with
|
|
49
|
+
* @otfdashkit/ui-native via the same `shimmer` variant key. */
|
|
50
|
+
shimmer: [
|
|
51
|
+
'relative isolate overflow-hidden',
|
|
52
|
+
'bg-primary text-primary-foreground',
|
|
53
|
+
'shadow-[0_1px_0_rgba(255,255,255,0.18)_inset,0_-1px_0_rgba(0,0,0,0.25)_inset]',
|
|
54
|
+
'hover:bg-primary/95',
|
|
55
|
+
// Continuous conic-gradient sweep behind the surface.
|
|
56
|
+
'before:absolute before:inset-[-1px] before:rounded-[inherit] before:-z-10',
|
|
57
|
+
'before:bg-[conic-gradient(from_0deg,transparent_0deg,transparent_70deg,rgba(255,255,255,0.55)_90deg,transparent_110deg,transparent_360deg)]',
|
|
58
|
+
'before:animate-[shimmer-rotate_2.6s_linear_infinite]',
|
|
59
|
+
// Diagonal gloss that sweeps on hover.
|
|
60
|
+
'after:absolute after:inset-0 after:rounded-[inherit] after:pointer-events-none',
|
|
61
|
+
'after:bg-[linear-gradient(115deg,transparent_30%,rgba(255,255,255,0.22)_50%,transparent_70%)]',
|
|
62
|
+
'after:translate-x-[-120%] hover:after:translate-x-[120%] after:transition-transform after:duration-[900ms] after:ease-out',
|
|
63
|
+
].join(' '),
|
|
46
64
|
},
|
|
47
65
|
size: {
|
|
48
66
|
sm: 'h-8 px-3 text-xs rounded-md',
|
|
@@ -106,7 +106,7 @@ export const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>(
|
|
|
106
106
|
return (
|
|
107
107
|
<div
|
|
108
108
|
ref={(node) => {
|
|
109
|
-
containerRef.current = node
|
|
109
|
+
(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
110
110
|
if (typeof ref === 'function') ref(node)
|
|
111
111
|
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
112
112
|
}}
|
|
@@ -100,7 +100,7 @@ export const Comparison = React.forwardRef<HTMLDivElement, ComparisonProps>(
|
|
|
100
100
|
return (
|
|
101
101
|
<div
|
|
102
102
|
ref={(node) => {
|
|
103
|
-
containerRef.current = node
|
|
103
|
+
(containerRef as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
104
104
|
if (typeof ref === 'function') ref(node)
|
|
105
105
|
else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node
|
|
106
106
|
}}
|
|
@@ -20,7 +20,11 @@ export type QrCodeProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'>
|
|
|
20
20
|
src: string
|
|
21
21
|
height: number
|
|
22
22
|
width: number
|
|
23
|
-
excavate
|
|
23
|
+
excavate: boolean
|
|
24
|
+
x?: number
|
|
25
|
+
y?: number
|
|
26
|
+
opacity?: number
|
|
27
|
+
crossOrigin?: 'anonymous' | 'use-credentials' | ''
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
|
package/src/themes/base.css
CHANGED
|
@@ -23,3 +23,21 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
23
23
|
line-height: var(--line-height-heading, 1.2);
|
|
24
24
|
font-weight: var(--font-weight-heading, 600);
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/* ── Animations shipped with the SDK ────────────────────────────────────────
|
|
28
|
+
Consumers get these automatically by importing '@otfdashkit/ui/styles'.
|
|
29
|
+
Keep keyframe names unique to avoid collision with consumer-defined ones. */
|
|
30
|
+
|
|
31
|
+
/* Conic-gradient shimmer rotation — used by Button variant="shimmer".
|
|
32
|
+
Animation: 360deg sweep, 2.6s linear, infinite. */
|
|
33
|
+
@keyframes shimmer-rotate {
|
|
34
|
+
0% { --shimmer-angle: 0deg; transform: rotate(0deg); }
|
|
35
|
+
100% { --shimmer-angle: 360deg; transform: rotate(360deg); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Concentric ripple expand — reserved for future use (RippleLoader, ping).
|
|
39
|
+
The existing motion/Ripple is click-triggered; this is for ambient pulses. */
|
|
40
|
+
@keyframes pulse-ring {
|
|
41
|
+
0% { transform: scale(0.4); opacity: 0.8; }
|
|
42
|
+
100% { transform: scale(1.6); opacity: 0; }
|
|
43
|
+
}
|