+
{children}
diff --git a/src/components/Tabs/tabs.scss b/src/components/Tabs/tabs.module.scss
similarity index 63%
rename from src/components/Tabs/tabs.scss
rename to src/components/Tabs/tabs.module.scss
index 2689155..ef56971 100644
--- a/src/components/Tabs/tabs.scss
+++ b/src/components/Tabs/tabs.module.scss
@@ -1,41 +1,40 @@
@import '../../scss/config.scss';
-.w-tabs {
-
- &.boxed .tabs,
- &.outline .tabs {
- background: #252525;
+.tabs {
+ &.boxed .items,
+ &.outline .items {
+ background: var(--w-color-primary-50);
border-radius: 5px;
padding: 5px;
- width: fit-content;
+ width: max-content;
button {
@include Transition();
border-radius: 5px;
padding: 10px;
- &.active {
+ &[data-active="true"] {
border: 0;
- background: #000;
+ background: var(--w-color-primary-70);
padding-bottom: 10px;
}
}
}
- &.outline .tabs {
+ &.outline .items {
background: transparent;
- border: 1px solid #252525;
+ border: 1px solid var(--w-color-primary-50);
button {
margin-bottom: 0;
- &.active {
- background: #252525;
+ &[data-active="true"] {
+ background: var(--w-color-primary-50);
}
}
}
- &.even .tabs button {
+ &.even .items button {
flex: 1;
justify-content: center;
}
@@ -44,36 +43,36 @@
display: flex;
gap: 20px;
- &.boxed .tabs button,
- &.outline .tabs button {
+ &.boxed .items button,
+ &.outline .items button {
border-bottom: 0;
}
- .tabs {
+ .items {
flex-direction: column;
gap: 5px;
button {
padding: 10px;
- border-bottom: 2px solid #252525;
+ border-bottom: 2px solid var(--w-color-primary-50);
- &.active {
+ &[data-active="true"] {
padding-bottom: 10px;
}
}
}
- .tab-content {
+ .content {
margin-top: 0;
}
}
- .tabs-wrapper {
+ .wrapper {
overflow: auto;
}
- .tabs {
- border-bottom: 2px solid #252525;
+ .items {
+ border-bottom: 2px solid var(--w-color-primary-50);
display: flex;
gap: 10px;
@@ -81,13 +80,13 @@
@include Transition(color);
background: transparent;
cursor: pointer;
- color: #FFF;
+ color: var(--w-color-primary);
border: 0;
font-size: 16px;
padding: 0;
margin-bottom: -2px;
padding: 15px 15px;
- color: #BBB;
+ color: var(--w-color-primary-20);
display: flex;
align-items: center;
gap: 10px;
@@ -100,19 +99,19 @@
}
&[disabled] {
- color: #555;
+ color: var(--w-color-primary-30);
cursor: no-drop;
}
- &.active {
- color: #FFF;
- border-bottom: 2px solid #FFF;
+ &[data-active="true"] {
+ color: var(--w-color-primary);
+ border-bottom: 2px solid var(--w-color-primary);
padding-bottom: 13px;
}
}
}
- .tab-content {
+ .content {
margin-top: 20px;
}
@@ -126,8 +125,8 @@
}
@include Media('xs') {
- .w-tabs {
- &.even .tabs {
+ .tabs {
+ &.even .items {
width: auto;
}
}
diff --git a/src/components/ThemeSwitcher/ThemeSwitcher.astro b/src/components/ThemeSwitcher/ThemeSwitcher.astro
new file mode 100644
index 0000000..92f65a3
--- /dev/null
+++ b/src/components/ThemeSwitcher/ThemeSwitcher.astro
@@ -0,0 +1,106 @@
+---
+import type { ThemeSwitcherProps } from './themeswitcher'
+import styles from './themeswitcher.module.scss'
+
+interface Props extends ThemeSwitcherProps {}
+
+const {
+ themes,
+ toggle,
+ size,
+ className
+} = Astro.props
+
+const classes = [
+ styles.switcher,
+ toggle && styles.toggle,
+ className
+]
+
+const primaryTheme = themes[Object.keys(themes)[0]]
+const secondaryTheme = themes[Object.keys(themes)[1]]
+const useIcons = Astro.slots.has('primaryIcon') && Astro.slots.has('secondaryIcon')
+
+const buttonClasses = [
+ styles.switch,
+ useIcons && styles.icons
+]
+---
+
+
+ {Object.keys(themes as {}).map((theme, index) => (
+
+ ))}
+
+
+
+
diff --git a/src/components/ThemeSwitcher/ThemeSwitcher.svelte b/src/components/ThemeSwitcher/ThemeSwitcher.svelte
new file mode 100644
index 0000000..11b632c
--- /dev/null
+++ b/src/components/ThemeSwitcher/ThemeSwitcher.svelte
@@ -0,0 +1,76 @@
+
+
+
+ {#each Object.keys(themes) as theme, index}
+
+ {/each}
+
diff --git a/src/components/ThemeSwitcher/ThemeSwitcher.tsx b/src/components/ThemeSwitcher/ThemeSwitcher.tsx
new file mode 100644
index 0000000..a85d08b
--- /dev/null
+++ b/src/components/ThemeSwitcher/ThemeSwitcher.tsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from 'react'
+import type { ReactThemeSwitcherProps } from './themeswitcher'
+
+import { setCookie, getCookie } from '../../utils/cookies'
+import { listen, dispatch } from '../../utils/event'
+import { classNames } from '../../utils/classNames'
+
+import styles from './themeswitcher.module.scss'
+
+const ThemeSwitcher = ({
+ themes,
+ toggle,
+ size,
+ primaryIcon,
+ secondaryIcon,
+ className
+}: ReactThemeSwitcherProps) => {
+ const [currentTheme, setCurrentTheme] = useState('')
+ const [toggled, setToggled] = useState(false)
+
+ const classes = classNames([
+ styles.switcher,
+ toggle && styles.toggle,
+ className
+ ])
+
+ const sizeStyles = size
+ ? { '--w-theme-switcher-size': `${size}px` } as React.CSSProperties
+ : undefined
+
+ const primaryTheme = themes[Object.keys(themes)[0]]
+ const secondaryTheme = themes[Object.keys(themes)[1]]
+ const useIcons = primaryIcon && secondaryIcon
+
+ const setTheme = (theme: string | number) => {
+ if (typeof theme === 'number') {
+ theme = toggled ? primaryTheme : secondaryTheme
+ }
+
+ document.body.classList.replace(currentTheme, theme)
+
+ setCookie('w-theme', theme, 30)
+ localStorage.setItem('w-theme', theme)
+
+ dispatch('theme-switched', theme)
+ }
+
+ useEffect(() => {
+ setCurrentTheme(
+ localStorage.getItem('w-theme')
+ || getCookie('w-theme')
+ || themes[Object.keys(themes)[0]]
+ )
+
+ if (toggle && currentTheme === secondaryTheme) {
+ setToggled(true)
+ }
+
+ listen('theme-switched', theme => {
+ setCurrentTheme(theme)
+ setToggled(theme === secondaryTheme)
+ })
+ }, [])
+
+ return (
+
+ {Object.keys(themes as {}).map((theme, index) => (
+
+ ))}
+
+ )
+}
+
+export default ThemeSwitcher
diff --git a/src/components/ThemeSwitcher/themeswitcher.module.scss b/src/components/ThemeSwitcher/themeswitcher.module.scss
new file mode 100644
index 0000000..02afce9
--- /dev/null
+++ b/src/components/ThemeSwitcher/themeswitcher.module.scss
@@ -0,0 +1,74 @@
+@import '../../scss/config.scss';
+
+.switcher {
+ display: flex;
+ gap: 10px;
+
+ &.toggle {
+ position: relative;
+ width: var(--w-theme-switcher-size);
+ height: var(--w-theme-switcher-size);
+
+ .switch {
+ position: absolute;
+
+ &:first-child {
+ z-index: 2;
+ }
+
+ &:nth-child(2) {
+ z-index: 1;
+ }
+
+ &[data-active="true"] {
+ z-index: 3;
+ }
+ }
+ }
+}
+
+.switch {
+ width: var(--w-theme-switcher-size);
+ height: var(--w-theme-switcher-size);
+ border-radius: 100%;
+ cursor: pointer;
+ position: relative;
+ border: 0;
+
+ &.icons {
+ @include Transition(opacity);
+ color: var(--w-color-primary);
+ background: transparent;
+ padding: 0;
+ opacity: 0;
+
+ &[data-active="true"] {
+ opacity: 1;
+ }
+
+ svg, img {
+ pointer-events: none;
+ width: var(--w-theme-switcher-size);
+ height: var(--w-theme-switcher-size);
+ }
+ }
+
+ &:not(.icons)::after {
+ @include Transition();
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ border: 1px solid var(--w-color-primary);
+ border-radius: 100%;
+ width: 0;
+ height: 0;
+ z-index: -1;
+ }
+
+ &[data-active="true"]::after {
+ width: calc(var(--w-theme-switcher-size) + 5px);
+ height: calc(var(--w-theme-switcher-size) + 5px);
+ }
+}
diff --git a/src/components/ThemeSwitcher/themeswitcher.ts b/src/components/ThemeSwitcher/themeswitcher.ts
new file mode 100644
index 0000000..6e2aa75
--- /dev/null
+++ b/src/components/ThemeSwitcher/themeswitcher.ts
@@ -0,0 +1,13 @@
+export type ThemeSwitcherProps = {
+ themes: {
+ [key: string]: string
+ }
+ toggle?: boolean
+ size?: number
+ className?: string
+}
+
+export type ReactThemeSwitcherProps = {
+ primaryIcon?: React.ReactNode
+ secondaryIcon?: React.ReactNode
+} & ThemeSwitcherProps
diff --git a/src/components/Timeline/Timeline.astro b/src/components/Timeline/Timeline.astro
index f1e92ef..6c3ecc0 100644
--- a/src/components/Timeline/Timeline.astro
+++ b/src/components/Timeline/Timeline.astro
@@ -1,6 +1,8 @@
---
import type { TimelineProps } from './timeline'
-import './timeline.scss'
+
+import styles from './timeline.module.scss'
+import { classNames } from '../../utils/classNames'
interface Props extends TimelineProps {}
@@ -15,20 +17,20 @@ const {
} = Astro.props
const classes = [
- 'w-timeline',
- theme,
- alternate && 'alternate',
- centered && 'centered',
+ styles.timeline,
+ theme && theme.split(' ').map(style => styles[style]),
+ alternate && styles.alternate,
+ centered && styles.centered,
className
]
-const styles = [
+const styleVariables = classNames([
color && `--w-timeline-color: ${color};`,
textColor && `--w-timeline-text-color: ${textColor};`,
counter && `--w-timeline-counter: ${counter};`,
-].join('')
+])
---
-
+
diff --git a/src/components/Timeline/Timeline.svelte b/src/components/Timeline/Timeline.svelte
index 8e2d93f..5be8b90 100644
--- a/src/components/Timeline/Timeline.svelte
+++ b/src/components/Timeline/Timeline.svelte
@@ -1,6 +1,8 @@
-
+
diff --git a/src/components/Timeline/Timeline.tsx b/src/components/Timeline/Timeline.tsx
index 4a083b4..d77e0a2 100644
--- a/src/components/Timeline/Timeline.tsx
+++ b/src/components/Timeline/Timeline.tsx
@@ -1,7 +1,8 @@
import React from 'react'
import type { ReactTimelineProps } from './timeline'
-import './timeline.scss'
+import styles from './timeline.module.scss'
+import { classNames } from '../../utils/classNames'
const Timeline = ({
theme,
@@ -13,22 +14,22 @@ const Timeline = ({
className,
children
}: ReactTimelineProps) => {
- const classes = [
- 'w-timeline',
- theme,
- alternate && 'alternate',
- centered && 'centered',
+ const classes = classNames([
+ styles.timeline,
+ theme && theme.split(' ').map(style => styles[style]),
+ alternate && styles.alternate,
+ centered && styles.centered,
className
- ].filter(Boolean).join(' ')
+ ])
- const styles = {
+ const styleVariables = {
...(color && { '--w-timeline-color': color }),
...(textColor && { '--w-timeline-text-color': textColor }),
...(counter && { '--w-timeline-counter': counter }),
} as React.CSSProperties
return (
-
+
)
diff --git a/src/components/Timeline/timeline.scss b/src/components/Timeline/timeline.module.scss
similarity index 93%
rename from src/components/Timeline/timeline.scss
rename to src/components/Timeline/timeline.module.scss
index d1944fb..d2dedcf 100644
--- a/src/components/Timeline/timeline.scss
+++ b/src/components/Timeline/timeline.module.scss
@@ -4,7 +4,7 @@
$leftOffset: calc(((25px + 4px) / 2) - 1px);
$rightOffset: calc((((25px + 4px) / 2) - 1px) * -1);
-.w-timeline {
+.timeline {
display: flex;
gap: 20px;
flex-direction: column;
@@ -17,7 +17,7 @@ $rightOffset: calc((((25px + 4px) / 2) - 1px) * -1);
&::before {
position: absolute;
content: '';
- width: 1px;
+ width: 2px;
background: var(--w-timeline-color);
height: 100%;
left: $leftOffset
@@ -28,13 +28,13 @@ $rightOffset: calc((((25px + 4px) / 2) - 1px) * -1);
}
&.stroke li::before {
- background: #000;
+ background: var(--w-color-primary-70);
border: 2px solid var(--w-timeline-color);
}
}
@include Media('xs') {
- .w-timeline {
+ .timeline {
&.alternate {
padding: 0;
diff --git a/src/components/TimelineItem/TimelineItem.astro b/src/components/TimelineItem/TimelineItem.astro
index 0b55d76..7027702 100644
--- a/src/components/TimelineItem/TimelineItem.astro
+++ b/src/components/TimelineItem/TimelineItem.astro
@@ -1,6 +1,7 @@
---
import type { TimelineItemProps } from './timelineitem'
-import './timelineitem.scss'
+
+import styles from './timelineitem.module.scss'
interface Props extends TimelineItemProps {}
@@ -11,7 +12,7 @@ const {
} = Astro.props
const classes = [
- 'w-timeline-item',
+ styles.item,
className
]
@@ -20,7 +21,7 @@ const Title = titleTag
-
{title && (
- {title}
+ {title}
)}
diff --git a/src/components/TimelineItem/TimelineItem.svelte b/src/components/TimelineItem/TimelineItem.svelte
index f996eaa..a6e06c9 100644
--- a/src/components/TimelineItem/TimelineItem.svelte
+++ b/src/components/TimelineItem/TimelineItem.svelte
@@ -1,20 +1,22 @@
-
{#if title}
-
+
{title}
{/if}
diff --git a/src/components/TimelineItem/TimelineItem.tsx b/src/components/TimelineItem/TimelineItem.tsx
index f109b6a..ca2b666 100644
--- a/src/components/TimelineItem/TimelineItem.tsx
+++ b/src/components/TimelineItem/TimelineItem.tsx
@@ -1,7 +1,8 @@
import React from 'react'
import type { TimelineItemProps } from './timelineitem'
-import './timelineitem.scss'
+import styles from './timelineitem.module.scss'
+import { classNames } from '../../utils/classNames'
type ReactTimelineItemProps = {
TitleTag?: keyof JSX.IntrinsicElements
@@ -14,15 +15,15 @@ const TimelineItem = ({
className,
children
}: ReactTimelineItemProps) => {
- const classes = [
- 'w-timeline-item',
+ const classes = classNames([
+ styles.item,
className
- ].filter(Boolean).join(' ')
+ ])
return (
-
{title && (
- {title}
+ {title}
)}
{children}
diff --git a/src/components/TimelineItem/timelineitem.scss b/src/components/TimelineItem/timelineitem.module.scss
similarity index 89%
rename from src/components/TimelineItem/timelineitem.scss
rename to src/components/TimelineItem/timelineitem.module.scss
index 7813be3..7a5c6ef 100644
--- a/src/components/TimelineItem/timelineitem.scss
+++ b/src/components/TimelineItem/timelineitem.module.scss
@@ -1,6 +1,6 @@
@import '../../scss/config.scss';
-.w-timeline-item {
+.item {
position: relative;
font-size: 16px;
@@ -18,11 +18,11 @@
display: inline-flex;
align-items: center;
justify-content: center;
- border: 2px solid #000;
+ border: 2px solid var(--w-color-primary-70);
margin-left: -40px;
}
- .timeline-title {
+ .title {
display: block;
margin-bottom: 10px;
font-family: Bold;
diff --git a/src/components/Toast/Toast.astro b/src/components/Toast/Toast.astro
index ee009c6..efee7a9 100644
--- a/src/components/Toast/Toast.astro
+++ b/src/components/Toast/Toast.astro
@@ -2,29 +2,40 @@
import type { ToastProps } from './toast'
import Alert from '../Alert/Alert.astro'
-import './toast.scss'
+import styles from './toast.module.scss'
+import { classNames } from '../../utils/classNames'
interface Props extends ToastProps {}
const {
position,
- className
+ className,
+ ...rest
} = Astro.props
-const classes = [
- 'w-toast',
- position,
+const classes = classNames([
+ styles.toast,
className
-].filter(Boolean).join(' ')
+])
+
+const additionalProps = {
+ ...(position && { 'data-position': position }),
+ titleProps: {
+ 'data-id': 'title'
+ },
+ bodyProps: {
+ 'data-id': 'body'
+ }
+}
---
{Astro.slots.has('icon') ? (
-
+
) : (
-
+
)}
diff --git a/src/components/Toast/Toast.svelte b/src/components/Toast/Toast.svelte
index 7eccc1a..899385b 100644
--- a/src/components/Toast/Toast.svelte
+++ b/src/components/Toast/Toast.svelte
@@ -1,19 +1,30 @@
-
+
{#if $$slots.icon}
{/if}
diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx
index 02b9c4d..6496f5e 100644
--- a/src/components/Toast/Toast.tsx
+++ b/src/components/Toast/Toast.tsx
@@ -2,7 +2,8 @@ import React from 'react'
import type { ReactToastProps } from './toast'
import Alert from '../Alert/Alert.tsx'
-import './toast.scss'
+import styles from './toast.module.scss'
+import { classNames } from '../../utils/classNames'
const Toast = ({
icon,
@@ -11,14 +12,23 @@ const Toast = ({
children,
...rest
}: ReactToastProps) => {
- const classes = [
- 'w-toast',
- position,
+ const classes = classNames([
+ styles.toast,
className
- ].filter(Boolean).join(' ')
+ ])
+
+ const additionalProps = {
+ ...(position && { 'data-position': position }),
+ titleProps: {
+ 'data-id': 'title'
+ },
+ bodyProps: {
+ 'data-id': 'body'
+ }
+ }
return (
-
+
{children}
diff --git a/src/components/Toast/toast.scss b/src/components/Toast/toast.module.scss
similarity index 55%
rename from src/components/Toast/toast.scss
rename to src/components/Toast/toast.module.scss
index cb71f73..456ccdc 100644
--- a/src/components/Toast/toast.scss
+++ b/src/components/Toast/toast.module.scss
@@ -1,43 +1,44 @@
@import '../../scss/config.scss';
-.w-toast {
- background: #000;
+.toast {
+ background: var(--w-color-primary-70);
position: fixed;
right: 20px;
bottom: 20px;
transform: translateY(calc(100% + 25px));
+ z-index: 99;
- &:not(.no-anim) {
+ &[data-show] {
@include Transition(transform);
}
- &.show {
+ &[data-show="true"] {
transform: translateY(0);
}
- &.bottom-left,
- &.top-left {
+ &[data-position="bottom-left"],
+ &[data-position="top-left"] {
right: auto;
left: 20px;
}
- &.top-left,
- &.top-right,
- &.top-full {
+ &[data-position="top-left"],
+ &[data-position="top-right"],
+ &[data-position="top-full"] {
bottom: auto;
top: 20px;
transform: translateY(calc(-100% - 25px));
- &.show {
+ &[data-show="true"] {
transform: translateY(0);
}
}
- &.bottom-full {
+ &[data-position="bottom-full"] {
left: 20px;
}
- &.top-full {
+ &[data-position="top-full"] {
left: 20px;
}
}
diff --git a/src/components/Toast/toast.ts b/src/components/Toast/toast.ts
index 1483e39..87f0e0d 100644
--- a/src/components/Toast/toast.ts
+++ b/src/components/Toast/toast.ts
@@ -1,11 +1,8 @@
-import type { AlertProps } from '../Alert/alert'
+import type { AlertProps, ReactAlertProps } from '../Alert/alert'
export type ToastProps = {
- position?: string
+ position?: 'bottom-left' | 'top-left' | 'top-right' | null
[key: string]: any
} & AlertProps
-export type ReactToastProps = {
- children: React.ReactNode
- icon?: string
-} & ToastProps
+export type ReactToastProps = ToastProps & ReactAlertProps
diff --git a/src/data.js b/src/data.js
index 0782bd0..1cc0595 100644
--- a/src/data.js
+++ b/src/data.js
@@ -52,3 +52,27 @@ export const overflowTabs = Array(10).fill({
label: `Tab ${index + 1}`,
value: `tab-${index + 1}`
}))
+
+export const menu = [
+ { name: 'Home', url: '/' },
+ { name: 'Docs', url: '/docs'}
+]
+
+export const menuLogo = {
+ url: '/img/logo.png',
+ width: 25,
+ height: 25
+}
+
+export const themes = {
+ '#252525': 'theme-dark',
+ '#DDD': 'theme-light',
+ '#415a77': 'theme-blue',
+ '#d5bdaf': 'theme-beige',
+ '#588157': 'theme-green'
+}
+
+export const toggleThemes = {
+ '#252525': 'theme-dark',
+ '#DDD': 'theme-light'
+}
diff --git a/src/helpers.js b/src/helpers.js
index bafd477..23263c9 100644
--- a/src/helpers.js
+++ b/src/helpers.js
@@ -1,26 +1,30 @@
export const getSections = ({
title,
components,
- showSubTitle = false
+ showSubTitle = false,
+ props = {}
}) => {
return [
{
title: `Astro ${title}`,
component: components[0],
+ ...(props && props)
},
{
title: `Svelte ${title}`,
component: components[1],
...(showSubTitle && {
subTitle: 'For interactive examples, visit Svelte Playground',
- })
+ }),
+ ...(props && props)
},
{
title: `React ${title}`,
component: components[2],
...(showSubTitle && {
subTitle: 'For interactive examples, visit React Playground',
- })
+ }),
+ ...(props && props)
}
]
}
diff --git a/src/icons/moon.svg b/src/icons/moon.svg
new file mode 100644
index 0000000..5eaed4d
--- /dev/null
+++ b/src/icons/moon.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/icons/sun.svg b/src/icons/sun.svg
new file mode 100644
index 0000000..5442f7d
--- /dev/null
+++ b/src/icons/sun.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/pages/alert.astro b/src/pages/alert.astro
index 3a9079f..d2540cd 100644
--- a/src/pages/alert.astro
+++ b/src/pages/alert.astro
@@ -70,6 +70,18 @@ const sections = getSections({
Alert element with custom theme and icon.
+
+
+ Predefined info theme alert without title.
+
+
+
+ Predefined success theme alert without title and two lines.
+
+
+
+ Predefined warning theme alert without title and multiple lines, to demonstrate the behavior of layout with large amounts of text.
+
))}
diff --git a/src/pages/avatar.astro b/src/pages/avatar.astro
index 8564c39..04d98e0 100644
--- a/src/pages/avatar.astro
+++ b/src/pages/avatar.astro
@@ -6,22 +6,13 @@ import AstroAvatar from '@components/Avatar/Avatar.astro'
import SvelteAvatar from '@components/Avatar/Avatar.svelte'
import ReactAvatar from '@components/Avatar/Avatar.tsx'
+import { getSections } from '@helpers'
import { avatarGroup } from '@data'
-const sections = [
- {
- title: 'Astro avatars',
- component: AstroAvatar
- },
- {
- title: 'Svelte avatars',
- component: SvelteAvatar
- },
- {
- title: 'React avatars',
- component: ReactAvatar
- }
-]
+const sections = getSections({
+ title: 'avatars',
+ components: [AstroAvatar, SvelteAvatar, ReactAvatar]
+})
---