nitro-web 0.0.15 → 0.0.17

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.
@@ -26,7 +26,7 @@
26
26
  @apply text-dark mb-2 text-sm font-bold;
27
27
  }
28
28
  label, .label {
29
- @apply text-sm-label font-medium;
29
+ @apply text-sm font-medium;
30
30
  }
31
31
  }
32
32
 
@@ -1,4 +1,3 @@
1
-
2
1
  import 'nitro-web/client/globals'
3
2
  import { setupApp } from 'nitro-web'
4
3
 
@@ -6,6 +5,5 @@ import './css/index.css'
6
5
  import config from './config'
7
6
  import { Layout1, Layout2 } from '../components/partials/layouts'
8
7
 
9
- // Auto-import page components, initilize app, and run config.beforeApp
8
+ // Auto-import page components, initialise app, and run config.beforeApp
10
9
  setupApp(config, [Layout1, Layout2])
11
-
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { css, theme } from 'twin.macro'
6
6
  import config from '../client/config'
7
+ import { isDemo } from 'nitro-web'
7
8
  import {
8
9
  Signin,
9
10
  Signup,
@@ -79,7 +80,7 @@ DashboardPage.route = {
79
80
  export const StyleguidePage = () => <Styleguide config={config} />
80
81
  StyleguidePage.route = {
81
82
  '/styleguide': true,
82
- 'meta': { title: 'Style Guide - Nitro', layout: 1 },
83
+ 'meta': { title: `${isDemo ? 'Design System' : 'Style Guide'} - Nitro`, layout: 1 },
83
84
  }
84
85
 
85
86
  // Not found page
@@ -139,9 +140,14 @@ export function PricingPage() {
139
140
  Choose the right plan for you
140
141
  </p>
141
142
  </div>
142
- <p className="mx-auto mt-6 max-w-2xl text-pretty text-center text-lg font-medium text-gray-600 sm:text-xl/8">
143
- Choose an affordable plan that’s packed with the best features for engaging your audience, creating customer
144
- loyalty, and driving sales.
143
+ <p className="mx-auto mt-6 max-w-3xl text-pretty text-center text-lg font-medium text-gray-600 sm:text-xl/8">
144
+ This is a&nbsp;
145
+ <a class="underline" href="https://tailwindui.com/components/marketing/sections/pricing" target="_blank" rel="noreferrer">
146
+ pricing page
147
+ </a> example using one of the&nbsp;
148
+ <a class="underline" href="https://tailwindui.com/components" target="_blank" rel="noreferrer">
149
+ Tailwind UI
150
+ </a> components. With minor composition changes, it&apos;s almost copy/paste.
145
151
  </p>
146
152
  <div className="mx-auto mt-16 grid max-w-lg grid-cols-1 items-center gap-y-6 sm:mt-20 sm:gap-y-0 lg:max-w-4xl lg:grid-cols-2">
147
153
  {tiers.map((tier, tierIdx) => (
@@ -1,9 +1,10 @@
1
- // https://github.com/tailwindlabs/tailwindcss/blob/main/stubs/config.full.js#L889
2
1
  import defaultTheme from 'tailwindcss/defaultTheme'
3
2
  import colors from 'tailwindcss/colors'
4
3
  import path from 'path'
5
4
  import Color from 'color'
5
+
6
6
  const lighten = (clr, val) => Color(clr).lighten(val).rgb().string()
7
+ const darken = (clr, val) => Color(clr).darken(val).rgb().string()
7
8
  const nitroDir = path.dirname(require.resolve('nitro-web'))
8
9
 
9
10
  export default {
@@ -15,23 +16,21 @@ export default {
15
16
  ],
16
17
  },
17
18
  experimental: {
18
- optimizeUniversalDefaults: true, // remove unneeded varables from universal selectors
19
+ optimizeUniversalDefaults: true, // remove undesired variables from universal selectors
19
20
  },
20
21
  theme: {
22
+ // Full list: https://github.com/tailwindlabs/tailwindcss/blob/main/stubs/config.full.js#L889
21
23
  extend: {
22
- // Nitro theme variables
23
24
  boxShadow: {
24
25
  'dropdown-ul': '0 2px 8px 0 rgba(0, 0, 0, 0.05)',
25
26
  },
26
27
  colors: {
27
28
  // Main colors
28
- 'primary': colors.indigo[500],
29
- 'primary-dark': colors.indigo[600],
30
- 'primary-light': colors.indigo[400],
31
- 'primary-hover': lighten(colors.indigo[500], 0.05),
29
+ 'primary': '#4c50f9',
30
+ 'primary-dark': darken('#4c50f9', 0.05),
31
+ 'primary-hover': lighten('#4c50f9', 0.05),
32
32
  'secondary': colors.green[500],
33
33
  'secondary-dark': colors.green[600],
34
- 'secondary-light': colors.green[400],
35
34
  'secondary-hover': lighten(colors.green[500], 0.05),
36
35
  'label': colors.gray[900],
37
36
  'link': colors.black,
@@ -40,6 +39,7 @@ export default {
40
39
  'light': colors.gray[100],
41
40
  'dark': colors.gray[900],
42
41
  // Alert colors
42
+ 'critical': '#ff0000',
43
43
  'danger': '#ff0000',
44
44
  'danger-dark': colors.red[800],
45
45
  'info': colors.blue[500],
@@ -54,15 +54,15 @@ export default {
54
54
  sans: ['Inter', ...defaultTheme.fontFamily.sans],
55
55
  },
56
56
  fontSize: {
57
- '2xs': ['0.77rem', { lineHeight: '1.5' }],
58
- 'xs': ['0.83rem', { lineHeight: '1.5' }],
59
- 'sm-label': ['0.87rem', { lineHeight: '1.5' }],
60
- 'sm': ['0.90rem', { lineHeight: '1.5' }],
61
- 'base': ['1rem', { lineHeight: '1.5' }],
62
- 'lg': ['1.16rem', { lineHeight: '1.75' }],
63
- 'xl': ['1.29rem', { lineHeight: '1.75' }],
64
- '2xl': ['1.45rem', { lineHeight: '1.75' }],
65
- '3xl': ['1.94rem', { lineHeight: '1.75' }],
57
+ '2xs': ['12px', { lineHeight: '1.5' }],
58
+ 'xs': ['13px', { lineHeight: '1.5' }],
59
+ 'sm': ['13.5px', { lineHeight: '1.5' }],
60
+ 'md': ['14px', { lineHeight: '1.5' }],
61
+ 'base': ['15.5px', { lineHeight: '1.5' }],
62
+ 'lg': ['18px', { lineHeight: '1.75' }],
63
+ 'xl': ['20px', { lineHeight: '1.75' }],
64
+ '2xl': ['22.5px', { lineHeight: '1.75' }],
65
+ '3xl': ['30px', { lineHeight: '1.75' }],
66
66
  },
67
67
  spacing: {
68
68
  'input-before': '0.625rem',
package/client/index.ts CHANGED
@@ -22,11 +22,12 @@ export { Styleguide } from '../components/partials/styleguide'
22
22
  export { Accordion } from '../components/partials/element/accordion'
23
23
  export { Avatar } from '../components/partials/element/avatar'
24
24
  export { Button } from '../components/partials/element/button'
25
+ export { Calendar, type CalendarProps } from '../components/partials/element/calendar'
25
26
  export { Dropdown } from '../components/partials/element/dropdown'
26
27
  export { GithubLink } from '../components/partials/element/github-link'
27
28
  export { Initials } from '../components/partials/element/initials'
28
29
  export { Message } from '../components/partials/element/message'
29
- // export { Modal } from '../components/partials/element/modal'
30
+ export { Modal } from '../components/partials/element/modal'
30
31
  export { Sidebar, type SidebarProps } from '../components/partials/element/sidebar'
31
32
  export { Tooltip } from '../components/partials/element/tooltip'
32
33
  export { Topbar } from '../components/partials/element/topbar'
@@ -36,10 +37,10 @@ export { Checkbox } from '../components/partials/form/checkbox'
36
37
  export { Drop } from '../components/partials/form/drop'
37
38
  export { DropHandler } from '../components/partials/form/drop-handler'
38
39
  export { FormError } from '../components/partials/form/form-error'
39
- export { Input } from '../components/partials/form/input'
40
- export { InputColor } from '../components/partials/form/input-color'
41
- export { InputCurrency } from '../components/partials/form/input-currency'
42
- // export { InputDate } from '../components/partials/form/input-date'
40
+ export { Field } from '../components/partials/form/input'
41
+ export { FieldColor, type FieldColorProps } from '../components/partials/form/input-color'
42
+ export { FieldCurrency, type FieldCurrencyProps } from '../components/partials/form/input-currency'
43
+ export { FieldDate, type FieldDateProps } from '../components/partials/form/input-date'
43
44
  export { Location } from '../components/partials/form/location'
44
45
  export { Select, getSelectStyle } from '../components/partials/form/select'
45
46
  export { Toggle } from '../components/partials/form/toggle'
@@ -50,3 +51,6 @@ export { Layout2 } from '../components/partials/layout/layout2'
50
51
 
51
52
  // Component Other
52
53
  export { IsFirstRender } from '../components/partials/is-first-render'
54
+
55
+ // IsDemo environment variable
56
+ export const isDemo = ISDEMO
@@ -1,4 +1,4 @@
1
- import { Topbar, Input, FormError, Button, util } from 'nitro-web'
1
+ import { Topbar, Field, FormError, Button, util } from 'nitro-web'
2
2
  import { Errors } from 'types'
3
3
 
4
4
  export function ResetInstructions() {
@@ -24,7 +24,7 @@ export function ResetInstructions() {
24
24
  <form onSubmit={onSubmit}>
25
25
  <div>
26
26
  <label for="email">Email Address</label>
27
- <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
27
+ <Field name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
28
28
  </div>
29
29
 
30
30
  <div class="mb-14">
@@ -67,11 +67,11 @@ export function ResetPassword() {
67
67
  <form onSubmit={onSubmit}>
68
68
  <div>
69
69
  <label for="password">Your New Password</label>
70
- <Input name="password" type="password" state={state} onChange={onChange.bind(setState)} />
70
+ <Field name="password" type="password" state={state} onChange={onChange.bind(setState)} />
71
71
  </div>
72
72
  <div>
73
73
  <label for="password2">Repeat Your New Password</label>
74
- <Input name="password2" type="password" state={state} onChange={onChange.bind(setState)} />
74
+ <Field name="password2" type="password" state={state} onChange={onChange.bind(setState)} />
75
75
  </div>
76
76
 
77
77
  <div class="mb-14">
@@ -1,4 +1,4 @@
1
- import { Topbar, Input, Button, FormError, util } from 'nitro-web'
1
+ import { Topbar, Field, Button, FormError, util } from 'nitro-web'
2
2
  import { Config, Errors } from 'types'
3
3
 
4
4
  export function Signin({ config }: { config: Config }) {
@@ -50,14 +50,14 @@ export function Signin({ config }: { config: Config }) {
50
50
  <form onSubmit={onSubmit}>
51
51
  <div>
52
52
  <label for="email">Email Address</label>
53
- <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
53
+ <Field name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
54
54
  </div>
55
55
  <div>
56
56
  <div class="flex justify-between">
57
57
  <label for="password">Password</label>
58
58
  <Link to="/reset" class="label underline2">Forgot?</Link>
59
59
  </div>
60
- <Input name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
60
+ <Field name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
61
61
  </div>
62
62
 
63
63
  <div class="mb-14">
@@ -1,4 +1,4 @@
1
- import { Button, Input, FormError, Topbar, util } from 'nitro-web'
1
+ import { Button, Field, FormError, Topbar, util } from 'nitro-web'
2
2
  import { Config, Errors } from 'types'
3
3
 
4
4
  export function Signup({ config }: { config: Config}) {
@@ -32,20 +32,20 @@ export function Signup({ config }: { config: Config}) {
32
32
  <div class="grid grid-cols-2 gap-6">
33
33
  <div>
34
34
  <label for="name">Your Name</label>
35
- <Input name="name" placeholder="E.g. Bruce Wayne" state={state} onChange={onChange.bind(setState)} />
35
+ <Field name="name" placeholder="E.g. Bruce Wayne" state={state} onChange={onChange.bind(setState)} />
36
36
  </div>
37
37
  <div>
38
38
  <label for="business.name">Company Name</label>
39
- <Input name="business.name" placeholder="E.g. Wayne Enterprises" state={state} onChange={onChange.bind(setState)} />
39
+ <Field name="business.name" placeholder="E.g. Wayne Enterprises" state={state} onChange={onChange.bind(setState)} />
40
40
  </div>
41
41
  </div>
42
42
  <div>
43
43
  <label for="email">Email Address</label>
44
- <Input name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
44
+ <Field name="email" type="email" state={state} onChange={onChange.bind(setState)} placeholder="Your email address..." />
45
45
  </div>
46
46
  <div>
47
47
  <label for="password">Password</label>
48
- <Input name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
48
+ <Field name="password" type="password" state={state} onChange={onChange.bind(setState)}/>
49
49
  </div>
50
50
 
51
51
  <div class="mb-14">
@@ -2,9 +2,9 @@ import { css, theme } from 'twin.macro'
2
2
 
3
3
  export function Dashboard({ config }: { config: { isStatic?: boolean } }) {
4
4
  const [store] = useTracked()
5
- const textColor = store.apiAvailable ? 'text-green-700' : 'text-pink-700'
6
- const fillColor = store.apiAvailable ? 'fill-green-500' : 'fill-pink-500'
7
- const bgColor = store.apiAvailable ? 'bg-green-100' : 'bg-pink-100'
5
+ const textColor = store.apiAvailable ? 'text-green-700' : config.isStatic ? 'text-gray-700' : 'text-pink-700'
6
+ const fillColor = store.apiAvailable ? 'fill-green-500' : config.isStatic ? 'fill-gray-500' : 'fill-pink-500'
7
+ const bgColor = store.apiAvailable ? 'bg-green-100' : config.isStatic ? 'bg-[#eeeeee]' : 'bg-pink-100'
8
8
 
9
9
  return (
10
10
  <div css={style}>
@@ -1,5 +1,5 @@
1
1
  import { twMerge } from 'tailwind-merge'
2
- import { ChevronDownIcon } from '@heroicons/react/20/solid'
2
+ import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'
3
3
 
4
4
  type Props = {
5
5
  color?: 'primary'|'secondary'|'white'
@@ -16,7 +16,7 @@ type Props = {
16
16
  export function Button({ color='primary', size='md', className, isLoading, IconLeft, IconRight, IconRight2, children, ...props }: Props) {
17
17
  // const size = (color.match(/xs|sm|md|lg/)?.[0] || 'md') as 'xs'|'sm'|'md'|'lg'
18
18
  const iconPosition = IconLeft ? 'left' : IconRight ? 'right' : IconRight2 ? 'right2' : 'none'
19
- const base = 'relative inline-block font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
19
+ const base = 'relative inline-block font-medium shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
20
20
 
21
21
  // Button types
22
22
  const primary = 'bg-primary text-white shadow-sm hover:bg-primary-hover focus-visible:outline-primary'
@@ -48,7 +48,8 @@ export function Button({ color='primary', size='md', className, isLoading, IconL
48
48
  const loading = isLoading ? '[&>*]:opacity-0 text-opacity-0' : ''
49
49
 
50
50
  function getIcon(Icon: React.ReactNode | 'v') {
51
- if (Icon == 'v') return <ChevronDownIcon className="size-6 -my-6 -mx-1" />
51
+ if (Icon == 'v' || Icon == 'down') return <ChevronDownIcon className="size-5 -my-6 -mx-1" />
52
+ if (Icon == '^' || Icon == 'up') return <ChevronUpIcon className="size-5 -my-6 -mx-1" />
52
53
  else return Icon
53
54
  }
54
55
 
@@ -0,0 +1,108 @@
1
+ import { DayPicker, getDefaultClassNames } from 'react-day-picker'
2
+ import { isValid } from 'date-fns'
3
+ import 'react-day-picker/style.css'
4
+ import { IsFirstRender } from 'nitro-web'
5
+
6
+ type Mode = 'single'|'multiple'|'range'
7
+ type ModeSelection<T extends Mode> = (
8
+ T extends 'single' ? Date | undefined
9
+ : T extends 'multiple' ? Date[]
10
+ : { from?: Date; to?: Date }
11
+ )
12
+
13
+ export type CalendarProps = {
14
+ mode?: Mode
15
+ onChange?: (mode: Mode, value: null|number|(null|number)[]) => void
16
+ value?: null|number|string|(null|number|string)[]
17
+ numberOfMonths?: number
18
+ month?: number // the value may be updated from an outside source, thus the month may have changed
19
+ className?: string
20
+ }
21
+
22
+ export function Calendar({ mode='single', onChange, value, numberOfMonths, month: monthProp, className }: CalendarProps) {
23
+ const isFirstRender = IsFirstRender()
24
+ const isRange = mode == 'range'
25
+
26
+ // Convert the value to an array of valid* dates
27
+ const dates = useMemo(() => {
28
+ const _dates = Array.isArray(value) ? value : [value]
29
+ return _dates.map(date => isValid(date) ? new Date(date as number) : undefined) ////change to null
30
+ }, [value])
31
+
32
+ // Hold the month in state to control the calendar when the input changes
33
+ const [month, setMonth] = useState(dates[0])
34
+
35
+ // Update the month if its changed from an outside source
36
+ useEffect(() => {
37
+ if (!isFirstRender && monthProp) setMonth(new Date(monthProp))
38
+ }, [monthProp])
39
+
40
+ function handleDayPickerSelect<T extends Mode>(newDate: ModeSelection<T>) {
41
+ switch (mode as T) {
42
+ case 'single': {
43
+ const date = newDate as ModeSelection<'single'>
44
+ onChange?.(mode, date?.getTime() ?? null)
45
+ break
46
+ }
47
+ case 'range': {
48
+ const { from, to } = (newDate ?? {}) as ModeSelection<'range'>
49
+ onChange?.(mode, from ? [from.getTime() || null, to?.getTime() || null] : null)
50
+ break
51
+ }
52
+ case 'multiple': {
53
+ const dates = (newDate as ModeSelection<'multiple'>)?.filter(Boolean) ?? []
54
+ onChange?.(mode, dates.map((d) => d.getTime()))
55
+ break
56
+ }
57
+ }
58
+ }
59
+
60
+ const d = getDefaultClassNames()
61
+ const common = {
62
+ month: month,
63
+ onMonthChange: setMonth,
64
+ onSelect: handleDayPickerSelect,
65
+ numberOfMonths: numberOfMonths || (isRange ? 2 : 1),
66
+ modifiersClassNames: {
67
+ // Add a class without _, TW seems to replace this with a space in the css definition, e.g. &:not(.range middle)
68
+ range_middle: `${d.range_middle} rangemiddle`,
69
+ },
70
+ classNames: {
71
+ root: `${d.root} flex`,
72
+ months: `${d.months} flex-nowrap`,
73
+ month_caption: `${d.month_caption} text-2xs pl-2`,
74
+ caption_label: `${d.caption_label} z-auto`,
75
+ button_previous: `${d.button_previous} size-8`,// [&:hover>svg]:fill-primary-dark`,
76
+ button_next: `${d.button_next} size-8`,// [&:hover>svg]:fill-primary-dark`,
77
+ chevron: `${d.chevron} fill-black size-[18px]`,
78
+
79
+ // Days
80
+ weekday: `${d.weekday} text-[11px] font-bold uppercase`,
81
+ day: `${d.day} size-[33px]`,
82
+ day_button: `${d.day_button} size-[33px] text-sm`,
83
+
84
+ // States
85
+ focused: `${d.focused} [&>button]:bg-gray-200 [&>button]:border-gray-200`,
86
+ range_start: `${d.range_start} [&>button]:!bg-primary-dark [&>button]:!border-primary-dark`,
87
+ range_end: `${d.range_end} [&>button]:!bg-primary-dark [&>button]:!border-primary-dark`,
88
+ selected: `${d.selected} font-normal `
89
+ + '[&:not(.rangemiddle)>button]:!text-white '
90
+ + '[&:not(.rangemiddle)>button]:!bg-primary-dark '
91
+ + '[&:not(.rangemiddle)>button]:!border-primary-dark ',
92
+ },
93
+ }
94
+
95
+ return (
96
+ <div>
97
+ {
98
+ mode === 'single' ? (
99
+ <DayPicker mode="single" selected={dates[0]} {...common} className={className} />
100
+ ) : mode === 'range' ? (
101
+ <DayPicker mode="range" selected={{ from: dates[0], to: dates[1] }} {...common} className={className} />
102
+ ) : (
103
+ <DayPicker mode="multiple" selected={dates.filter((d) => !!d)} {...common} className={className} />
104
+ )
105
+ }
106
+ </div>
107
+ )
108
+ }