fulldev-ui 0.5.0-beta.5 → 0.5.0-beta.6

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.
@@ -0,0 +1,146 @@
1
+ ---
2
+ import Button from './Button.astro'
3
+ import Element from './Element.astro'
4
+ import Price from './Price.astro'
5
+ import Text from './Text.astro'
6
+
7
+ interface Props {}
8
+
9
+ const { ...rest } = Astro.props
10
+ ---
11
+
12
+ <Element
13
+ size="md"
14
+ class="cart-summary"
15
+ >
16
+ <div
17
+ class="row"
18
+ {...rest}
19
+ >
20
+ <Text
21
+ class:list="total"
22
+ text="Totaal"
23
+ contrast
24
+ />
25
+ <Price value={10} />
26
+ </div>
27
+ <Text
28
+ class:list="shipping"
29
+ text="Verzendkosten worden berekend bij het afrekenen."
30
+ />
31
+ <Button
32
+ class:list="checkout-button"
33
+ variant="primary"
34
+ color="brand"
35
+ text="Afrekenen"
36
+ />
37
+ </Element>
38
+
39
+ <style is:global lang="scss">
40
+ @layer fulldev {
41
+ .cart-summary {
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: var(--space-3);
45
+
46
+ .price,
47
+ .total {
48
+ font-size: var(--text-4);
49
+ }
50
+
51
+ .row {
52
+ display: flex;
53
+ justify-content: space-between;
54
+ align-items: center;
55
+ }
56
+
57
+ .shipping {
58
+ font-size: var(--text-1);
59
+ }
60
+ }
61
+ }
62
+ </style>
63
+
64
+ <script>
65
+ import { $cart } from 'fulldev-ui/stores/cart.ts'
66
+ import { formatPrice } from 'fulldev-ui/utils/cart'
67
+ import Stripe from 'stripe'
68
+
69
+ const stripe = new Stripe(import.meta.env.PUBLIC_STRIPE_CHECKOUT_KEY)
70
+
71
+ function setPrice(items: any) {
72
+ const price = document.querySelector('.cart-summary .price')
73
+ if (price)
74
+ price.textContent = formatPrice(
75
+ items.reduce(
76
+ (acc: number, item: { price: number }) => acc + item.price,
77
+ 0
78
+ )
79
+ )
80
+ }
81
+
82
+ document.addEventListener('astro:page-load', () => {
83
+ setPrice($cart.get())
84
+
85
+ $cart.subscribe((items: any) => {
86
+ setPrice(items)
87
+ })
88
+
89
+ const button = document.querySelector('.cart-summary .checkout-button')
90
+
91
+ button?.addEventListener('click', async () => {
92
+ const items = $cart.get()
93
+
94
+ const checkoutConfig = {
95
+ success_url: `${import.meta.env.PUBLIC_SITE_URL}/checkout`,
96
+ mode: 'payment',
97
+ customer_creation: 'always',
98
+ shipping_address_collection: {
99
+ allowed_countries: ['NL'],
100
+ },
101
+ phone_number_collection: {
102
+ enabled: true,
103
+ },
104
+ }
105
+
106
+ try {
107
+ const lineItems = items.map((item: any) => ({
108
+ price_data: {
109
+ currency: 'eur',
110
+ product: item.id,
111
+ },
112
+ quantity: 1,
113
+ }))
114
+
115
+ // @ts-ignore
116
+ const checkoutSession = await stripe.checkout.sessions.create({
117
+ line_items: lineItems,
118
+ ...checkoutConfig,
119
+ })
120
+
121
+ checkoutSession.url && window.location.replace(checkoutSession.url)
122
+ } catch (error) {
123
+ console.error(error)
124
+
125
+ const lineItems = items.map((item: any) => ({
126
+ quantity: 1,
127
+ price_data: {
128
+ currency: 'eur',
129
+ product_data: {
130
+ name: item.name,
131
+ },
132
+ unit_amount: Math.round(item.price * 100),
133
+ },
134
+ }))
135
+
136
+ // @ts-ignore
137
+ const checkoutSession = await stripe.checkout.sessions.create({
138
+ line_items: lineItems,
139
+ ...checkoutConfig,
140
+ })
141
+
142
+ checkoutSession.url && window.location.replace(checkoutSession.url)
143
+ }
144
+ })
145
+ })
146
+ </script>
@@ -75,6 +75,12 @@ const {
75
75
  <Menu groups={menu} />
76
76
  <slot />
77
77
  </Element>
78
+ <Element
79
+ class:list="drawer-footer"
80
+ as="footer"
81
+ >
82
+ <slot name="footer" />
83
+ </Element>
78
84
  </Element>
79
85
  </Element>
80
86
 
@@ -99,7 +105,7 @@ const {
99
105
  max-height: 100%;
100
106
 
101
107
  @media (min-width: 768px) {
102
- width: 360px;
108
+ width: 440px;
103
109
  }
104
110
 
105
111
  &::backdrop {
@@ -108,19 +114,35 @@ const {
108
114
  }
109
115
 
110
116
  .drawer-body {
117
+ display: flex;
111
118
  flex-shrink: 1;
119
+ flex-direction: column;
112
120
  border-right: 1px solid var(--color-a6);
113
121
  height: 100%;
114
122
  overflow: auto;
115
123
  }
116
124
 
117
- .drawer-header {
125
+ .drawer-content {
126
+ display: flex;
127
+ flex-direction: column;
128
+ padding: var(--space-6) var(--gutter);
129
+ }
130
+
131
+ .drawer-header,
132
+ .drawer-footer {
118
133
  display: flex;
119
134
  position: sticky;
135
+ flex-shrink: 0;
136
+ z-index: 1;
137
+ border-style: solid;
138
+ border-color: var(--color-a6);
139
+ background-color: var(--base-background);
140
+ }
141
+
142
+ .drawer-header {
120
143
  top: 0;
121
144
  align-items: center;
122
- border-bottom: 1px solid var(--color-a6);
123
- background-color: var(--base-background);
145
+ border-bottom-width: 1px;
124
146
  padding: 0 var(--gutter);
125
147
  height: 56px;
126
148
 
@@ -129,10 +151,12 @@ const {
129
151
  }
130
152
  }
131
153
 
132
- .drawer-content {
133
- display: flex;
154
+ .drawer-footer {
155
+ bottom: 0;
134
156
  flex-direction: column;
135
- padding: var(--space-6) var(--gutter);
157
+ margin-top: auto;
158
+ border-top-width: 1px;
159
+ padding: var(--space-5) var(--gutter);
136
160
  }
137
161
  }
138
162
  }
@@ -0,0 +1,49 @@
1
+ ---
2
+ import type { ComponentProps, HTMLAttributes } from 'astro/types'
3
+ import Button from 'fulldev-ui/components/Button.astro'
4
+ import Element from 'fulldev-ui/components/Element.astro'
5
+ import Input from 'fulldev-ui/components/Input.astro'
6
+ import Select from 'fulldev-ui/components/Select.astro'
7
+ import Textarea from 'fulldev-ui/components/Textarea.astro'
8
+ import type { GlobalProps } from 'fulldev-ui/types/global-props'
9
+
10
+ interface Props extends GlobalProps, HTMLAttributes<'form'> {
11
+ fields: (
12
+ | ComponentProps<typeof Input>
13
+ | ComponentProps<typeof Select>
14
+ | (ComponentProps<typeof Textarea> & { type?: 'textarea' | undefined })
15
+ )[]
16
+ submit?: ComponentProps<typeof Button>['text']
17
+ }
18
+
19
+ const { fields, submit, ...rest } = Astro.props
20
+ ---
21
+
22
+ <Element
23
+ class:list="form"
24
+ as="form"
25
+ {...rest}
26
+ >
27
+ {
28
+ fields.map((field) => {
29
+ if ('options' in field) return <Select {...field} />
30
+ if ('type' in field && field.type === 'textarea')
31
+ return <Textarea {...field} />
32
+ return <Input {...(field as any)} />
33
+ })
34
+ }
35
+ <Button
36
+ type="submit"
37
+ text={submit}
38
+ />
39
+ </Element>
40
+
41
+ <style is:global lang="scss">
42
+ .form {
43
+ display: flex;
44
+ flex-direction: column;
45
+ gap: var(--space-4);
46
+ width: 100%;
47
+ max-width: calc(var(--space-4) * 44);
48
+ }
49
+ </style>
@@ -3,9 +3,9 @@ import type { ComponentProps, HTMLAttributes } from 'astro/types'
3
3
  import Buttons from 'fulldev-ui/components/Buttons.astro'
4
4
  import Element from 'fulldev-ui/components/Element.astro'
5
5
  import Logo from 'fulldev-ui/components/experimental/Logo.astro'
6
- import Search from 'fulldev-ui/components/Search.astro'
7
6
  import ThemeSelect from 'fulldev-ui/components/ThemeSelect.astro'
8
7
  import type { GlobalProps } from 'fulldev-ui/types/global-props'
8
+ import Cart from './Cart.astro'
9
9
  import Drawer from './Drawer.astro'
10
10
  import type Menu from './Menu.astro'
11
11
 
@@ -52,7 +52,6 @@ const {
52
52
  {buttons}
53
53
  variant="link"
54
54
  />
55
- {search && <Search />}
56
55
  {themeselect && <ThemeSelect />}
57
56
  <Drawer
58
57
  icon="menu-2"
@@ -61,13 +60,7 @@ const {
61
60
  {logo}
62
61
  {company}
63
62
  />
64
- <Drawer
65
- icon="shopping-cart"
66
- class="cart-button"
67
- color="brand"
68
- id="cart"
69
- heading={cart}
70
- />
63
+ <Cart heading={cart} />
71
64
  </Element>
72
65
 
73
66
  <style lang="scss" is:global>
@@ -17,6 +17,7 @@ const {
17
17
  disabled,
18
18
  required,
19
19
  placeholder,
20
+ readonly,
20
21
  name = placeholder,
21
22
  type = 'text',
22
23
  ...rest
@@ -41,6 +42,7 @@ const {
41
42
  {value}
42
43
  {disabled}
43
44
  {required}
45
+ {readonly}
44
46
  />
45
47
  <Icon name={icon} />
46
48
  </Element>
@@ -97,6 +99,10 @@ const {
97
99
  cursor: not-allowed;
98
100
  pointer-events: none;
99
101
  }
102
+
103
+ &[readonly] {
104
+ pointer-events: none;
105
+ }
100
106
  }
101
107
  }
102
108
  }
@@ -2,6 +2,7 @@
2
2
  import type { HTMLAttributes } from 'astro/types'
3
3
  import Element from 'fulldev-ui/components/Element.astro'
4
4
  import type { GlobalProps } from 'fulldev-ui/types/global-props'
5
+ import { formatPrice } from 'fulldev-ui/utils/cart'
5
6
 
6
7
  interface Props extends GlobalProps, HTMLAttributes<'div'> {
7
8
  size?: 'sm' | 'md' | 'lg' | undefined
@@ -13,17 +14,12 @@ interface Props extends GlobalProps, HTMLAttributes<'div'> {
13
14
  const { value, ...rest } = Astro.props
14
15
  ---
15
16
 
16
- {
17
- value && (
18
- <Element
19
- if={value}
20
- class:list="price"
21
- {...rest}
22
- >
23
- €{value?.toFixed(2).replace('.', ',')}
24
- </Element>
25
- )
26
- }
17
+ <Element
18
+ class:list="price"
19
+ {...rest}
20
+ >
21
+ {value && <Fragment set:html={formatPrice(value)} />}
22
+ </Element>
27
23
 
28
24
  <style is:global lang="scss">
29
25
  @layer fulldev {
@@ -1,5 +1,4 @@
1
1
  ---
2
- import Pagefind from 'astro-pagefind/components/Search'
3
2
  import Dialog from 'fulldev-ui/components/Dialog.astro'
4
3
 
5
4
  const { variant = 'secondary', ...rest } = Astro.props
@@ -11,9 +10,7 @@ const { variant = 'secondary', ...rest } = Astro.props
11
10
  icon="search"
12
11
  {variant}
13
12
  {...rest}
14
- >
15
- <Pagefind uiOptions={{ showImages: false }} />
16
- </Dialog>
13
+ />
17
14
 
18
15
  <style is:global lang="scss">
19
16
  :root {
@@ -1,21 +1,45 @@
1
1
  ---
2
- import type { HTMLAttributes } from 'astro/types'
2
+ import type { ComponentProps, HTMLAttributes } from 'astro/types'
3
3
  import Element from 'fulldev-ui/components/Element.astro'
4
4
  import type { GlobalProps } from 'fulldev-ui/types/global-props'
5
+ import Button from '../Button.astro'
6
+ import Input from '../Input.astro'
7
+ import Select from '../Select.astro'
8
+ import Textarea from '../Textarea.astro'
5
9
 
6
10
  interface Props extends GlobalProps, HTMLAttributes<'form'> {
7
- structure?: 'column' | 'row' | 'group' | undefined
11
+ submit?: ComponentProps<typeof Button>['text']
12
+ fields?:
13
+ | (
14
+ | ComponentProps<typeof Input>
15
+ | ComponentProps<typeof Select>
16
+ | (ComponentProps<typeof Textarea> & { type?: 'textarea' | undefined })
17
+ )[]
18
+ | undefined
8
19
  }
9
20
 
10
- const { structure = 'column', ...rest } = Astro.props
21
+ const { fields, submit, ...rest } = Astro.props
11
22
  ---
12
23
 
13
24
  <Element
14
- class:list={`form ${structure}`}
25
+ class:list="form"
15
26
  as="form"
16
27
  {...rest}
17
28
  >
18
- <slot />
29
+ <slot>
30
+ {
31
+ fields?.map((field) => {
32
+ if ('options' in field) return <Select {...field} />
33
+ if ('type' in field && field.type === 'textarea')
34
+ return <Textarea {...field} />
35
+ return <Input {...(field as any)} />
36
+ })
37
+ }
38
+ <Button
39
+ type="submit"
40
+ text={submit}
41
+ />
42
+ </slot>
19
43
  </Element>
20
44
 
21
45
  <style is:global lang="scss">
@@ -2,6 +2,7 @@ import { defineCollection } from 'astro:content'
2
2
  import imageLoader from 'fulldev-ui/loaders/imageLoader.ts'
3
3
  import imageSchema from 'fulldev-ui/schemas/imageSchema.ts'
4
4
  import pageSchema from 'fulldev-ui/schemas/pageSchema.ts'
5
+ import presetSchema from 'fulldev-ui/schemas/presetSchema.ts'
5
6
 
6
7
  export const collections = {
7
8
  pages: defineCollection({
@@ -10,7 +11,7 @@ export const collections = {
10
11
  }),
11
12
  presets: defineCollection({
12
13
  type: 'data',
13
- schema: pageSchema,
14
+ schema: presetSchema,
14
15
  }),
15
16
  images: defineCollection({
16
17
  loader: imageLoader,
@@ -2,14 +2,6 @@
2
2
  presets: [docs]
3
3
 
4
4
  title: Select
5
-
6
- example:
7
- label: Select
8
- name: select
9
- placeholder: Select an option
10
- options:
11
- - Option 1
12
- - Option 2
13
5
  ---
14
6
 
15
7
  # Select
@@ -2,11 +2,6 @@
2
2
  presets: [docs]
3
3
 
4
4
  title: Textarea
5
-
6
- example:
7
- name: textarea
8
- placeholder: Write a message..
9
- label: Textarea
10
5
  ---
11
6
 
12
7
  # Textarea
@@ -1,5 +1,4 @@
1
1
  component: DocsLayout
2
- presets: [base]
3
2
 
4
3
  sidebar: true
5
4
  header: false
package/src/env.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  /// <reference types="astro/client" />
2
- /// <reference path="../.astro/types.d.ts" />
2
+ /// <reference path="../.astro/types.d.ts" />
@@ -7,6 +7,7 @@ import Footer from 'fulldev-ui/blocks/Footer.astro'
7
7
  import Element from 'fulldev-ui/components/Element.astro'
8
8
  import Head from 'fulldev-ui/components/Head.astro'
9
9
  import Header from 'fulldev-ui/components/Header.astro'
10
+ import Section from 'fulldev-ui/components/Section.astro'
10
11
  import Sections from 'fulldev-ui/components/sections.astro'
11
12
  import Sidebar from 'fulldev-ui/components/Sidebar.astro'
12
13
  import Toc from 'fulldev-ui/components/Toc.astro'
@@ -19,6 +20,7 @@ type Props = GlobalProps &
19
20
  header?: ComponentProps<typeof Header>
20
21
  footer?: ComponentProps<typeof Footer>
21
22
  sections?: ComponentProps<typeof Sections>['sections']
23
+ section?: ComponentProps<typeof Section> | undefined
22
24
  banner?: any
23
25
  toc?: any
24
26
  sidebar?: any
@@ -47,6 +49,7 @@ const {
47
49
  toc,
48
50
  sections,
49
51
  theme,
52
+ section,
50
53
  banner,
51
54
  code,
52
55
  themeselect,
@@ -84,6 +87,7 @@ const {
84
87
  </slot>
85
88
  <main data-pagefind-body>
86
89
  <slot>
90
+ <Section {...section} />
87
91
  <Article />
88
92
  <Sections {sections} />
89
93
  </slot>
@@ -4,6 +4,9 @@ import fulldevConfig from 'virtual:fulldev-ui/config'
4
4
  import footer from './components/footer'
5
5
  import header from './components/header'
6
6
  import image from './components/image'
7
+ import images from './components/images'
8
+ import price from './components/price'
9
+ import section from './components/section'
7
10
  import sections from './components/sections'
8
11
  import sidebar from './components/sidebar'
9
12
  import toc from './components/toc'
@@ -12,7 +15,7 @@ import navigation from './utils/navigation'
12
15
  import parents from './utils/parents'
13
16
  import pathSchema from './utils/pathSchema'
14
17
 
15
- const i18n = z
18
+ export const i18n = z
16
19
  .object({
17
20
  i18n: pathSchema('pages').optional(),
18
21
  })
@@ -24,7 +27,7 @@ const i18n = z
24
27
  return merged as typeof rest
25
28
  })
26
29
 
27
- const presets = z
30
+ export const presets = z
28
31
  .object({
29
32
  presets: z.array(pathSchema('presets')).optional(),
30
33
  })
@@ -48,29 +51,51 @@ const presets = z
48
51
  return mergedData as typeof rest
49
52
  })
50
53
 
51
- export default i18n.pipe(presets).pipe(
52
- z
53
- .object({
54
- component,
55
- parents,
56
- theme: z.enum(['light', 'dark']),
57
- lang: z.string(),
58
- seo: z
59
- .object({
60
- title: z.string().optional(),
61
- description: z.string().optional(),
62
- })
63
- .optional(),
64
- title: z.string(),
65
- description: z.string().optional(),
66
- image,
67
- sections,
68
- footer,
69
- header: z.boolean().or(header),
70
- sidebar: z.boolean().or(sidebar),
71
- navigation,
72
- toc,
73
- })
74
- .partial()
75
- .passthrough()
76
- )
54
+ export const basePreset = z
55
+ .object({})
56
+ .passthrough()
57
+ .transform(async (data) => {
58
+ if (!fulldevConfig.basePreset) return data
59
+ const baseEntry = await getEntry('presets', fulldevConfig.basePreset)
60
+ const mergedData = assign(baseEntry?.data ?? {}, data)
61
+ return mergedData as typeof data
62
+ })
63
+
64
+ export const page = z
65
+ .object({
66
+ component,
67
+ parents,
68
+ theme: z.enum(['light', 'dark']),
69
+ lang: z.string(),
70
+ seo: z
71
+ .object({
72
+ title: z.string().optional(),
73
+ description: z.string().optional(),
74
+ })
75
+ .optional(),
76
+ title: z.string(),
77
+ description: z.string().optional(),
78
+ image,
79
+ images,
80
+ section,
81
+ sections,
82
+ footer,
83
+ header: z.boolean().or(header),
84
+ sidebar: z.boolean().or(sidebar),
85
+ navigation,
86
+ toc,
87
+ price,
88
+ code: z.any(),
89
+ sizes: z.any(),
90
+ banner: z.any(),
91
+ _schema: z.any(),
92
+ uitverkocht: z.any(),
93
+ rating: z.any(),
94
+ variants: z.any(),
95
+ heading: z.any(),
96
+ href: z.any(),
97
+ })
98
+ .partial()
99
+ .strict()
100
+
101
+ export default i18n.pipe(basePreset).pipe(presets).pipe(page)
@@ -0,0 +1,3 @@
1
+ import { page } from './pageSchema'
2
+
3
+ export default page
@@ -0,0 +1,23 @@
1
+ import { persistentAtom } from '@nanostores/persistent'
2
+ import { computed } from 'nanostores'
3
+
4
+ // type Item = CollectionEntry<'products'>['data'] & {
5
+ // quantity: number
6
+ // }
7
+
8
+ type Item = any
9
+
10
+ export const $cart = persistentAtom<Item[] | []>('cart', [], {
11
+ encode: JSON.stringify,
12
+ decode: JSON.parse,
13
+ })
14
+
15
+ // export const cartIsOpen = atom(false)
16
+
17
+ export const $totalPrice = computed($cart, ($cart) =>
18
+ $cart.reduce((a, b) => +a + b.price * b.quantity, 0)
19
+ )
20
+
21
+ // export const totalQuantity = computed(cart, ($cart) =>
22
+ // $cart.reduce((a, b) => +a + b.quantity, 0)
23
+ // )