agroptima-design-system 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.
@@ -10,7 +10,7 @@ permissions:
10
10
  contents: write
11
11
 
12
12
  jobs:
13
- chromatic-deployment:
13
+ github-pages-deployment:
14
14
  runs-on: ubuntu-latest
15
15
 
16
16
  steps:
@@ -19,15 +19,15 @@ jobs:
19
19
 
20
20
  - name: Install dependencies and build
21
21
  run: |
22
- npm install --workspace=@wineries/design-system
23
- npm run design-system:build
22
+ npm install
23
+ npm run build
24
24
  - name: Add robots.txt
25
25
  run: |
26
- touch apps/design-system/storybook-static/robots.txt
27
- echo "User-agent: *" >> apps/design-system/storybook-static/robots.txt
28
- echo "Disallow: /" >> apps/design-system/storybook-static/robots.txt
26
+ touch storybook-static/robots.txt
27
+ echo "User-agent: *" >> storybook-static/robots.txt
28
+ echo "Disallow: /" >> storybook-static/robots.txt
29
29
 
30
30
  - name: Deploy 🚀
31
31
  uses: JamesIves/github-pages-deploy-action@v4
32
32
  with:
33
- folder: apps/design-system/storybook-static
33
+ folder: storybook-static
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 18.18
@@ -1,59 +1,59 @@
1
- import type { StorybookConfig } from "@storybook/nextjs";
2
- import { join, dirname, resolve } from "path";
1
+ import type { StorybookConfig } from '@storybook/nextjs'
2
+ import { join, dirname, resolve } from 'path'
3
3
 
4
4
  /**
5
5
  * This function is used to resolve the absolute path of a package.
6
6
  * It is needed in projects that use Yarn PnP or are set up within a monorepo.
7
7
  */
8
8
  function getAbsolutePath(value: string): any {
9
- return dirname(require.resolve(join(value, "package.json")));
9
+ return dirname(require.resolve(join(value, 'package.json')))
10
10
  }
11
11
  const config: StorybookConfig = {
12
- stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
12
+ stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
13
13
  addons: [
14
- getAbsolutePath("@storybook/addon-links"),
15
- getAbsolutePath("@storybook/addon-essentials"),
16
- getAbsolutePath("@storybook/addon-onboarding"),
17
- getAbsolutePath("@storybook/addon-interactions"),
14
+ getAbsolutePath('@storybook/addon-links'),
15
+ getAbsolutePath('@storybook/addon-essentials'),
16
+ getAbsolutePath('@storybook/addon-onboarding'),
17
+ getAbsolutePath('@storybook/addon-interactions'),
18
18
  '@storybook/addon-a11y',
19
19
  '@storybook/addon-designs',
20
20
  ],
21
21
  framework: {
22
- name: getAbsolutePath("@storybook/nextjs"),
22
+ name: getAbsolutePath('@storybook/nextjs'),
23
23
  options: {},
24
24
  },
25
25
  docs: {
26
- autodocs: "tag",
26
+ autodocs: 'tag',
27
27
  },
28
28
  core: {
29
- builder: '@storybook/builder-webpack5'
29
+ builder: '@storybook/builder-webpack5',
30
30
  },
31
31
  webpackFinal: async (config) => {
32
- const pathToInlineSvg = resolve(__dirname, "../src/icons");
33
-
34
- config.module = config.module || {};
35
- config.module.rules = config.module.rules || [];
32
+ const pathToInlineSvg = resolve(__dirname, '../src/icons')
33
+
34
+ config.module = config.module || {}
35
+ config.module.rules = config.module.rules || []
36
36
 
37
37
  // This modifies the existing image rule to exclude .svg files
38
38
  // since you want to handle those files with @svgr/webpack
39
- const imageRule = config.module.rules.find((rule) => rule?.['test']?.test('.svg'));
39
+ const imageRule = config.module.rules.find(
40
+ (rule) => rule?.['test']?.test('.svg'),
41
+ )
40
42
  if (!imageRule || typeof imageRule !== 'object') return
41
43
 
42
44
  // Configure .svg files to be loaded with @svgr/webpack
43
- config.module.rules.push(
44
- {
45
+ config.module.rules.push({
45
46
  ...imageRule,
46
47
  test: /\.svg$/i,
47
48
  include: pathToInlineSvg,
48
49
  use: ['@svgr/webpack'],
49
- },
50
- );
50
+ })
51
51
 
52
52
  if (imageRule) {
53
- imageRule['exclude'] = /\.svg$/i;
53
+ imageRule['exclude'] = /\.svg$/i
54
54
  }
55
55
 
56
- return config;
56
+ return config
57
57
  },
58
- };
59
- export default config;
58
+ }
59
+ export default config
@@ -11,6 +11,17 @@ const preview: Preview = {
11
11
  },
12
12
  expanded: true,
13
13
  },
14
+ options: {
15
+ storySort: {
16
+ order: [
17
+ 'Welcome',
18
+ 'Changelog',
19
+ 'Component creation workflow',
20
+ 'Programmers start guide',
21
+ '*',
22
+ ],
23
+ },
24
+ },
14
25
  viewport: {
15
26
  viewports: INITIAL_VIEWPORTS,
16
27
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroptima-design-system",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -8,7 +8,7 @@
8
8
  "build-storybook": "storybook build",
9
9
  "lint": "eslint",
10
10
  "lint:fix": "eslint src --fix",
11
- "chromatic": "npx chromatic --exit-zero-on-changes"
11
+ "publish-chromatic": "npx chromatic --exit-zero-on-changes"
12
12
  },
13
13
  "dependencies": {
14
14
  "@storybook/addon-designs": "^7.0.5",
@@ -0,0 +1,99 @@
1
+ @use '../settings/color_alias';
2
+ @use '../settings/typography';
3
+ @use '../settings/config';
4
+
5
+ .cards-table-list {
6
+ width: 100%;
7
+ border-collapse: separate;
8
+ border-spacing: 0 config.$space-3x;
9
+
10
+ thead {
11
+ background: transparent;
12
+ }
13
+
14
+ th,
15
+ td {
16
+ overflow: hidden;
17
+ text-overflow: ellipsis;
18
+ }
19
+
20
+ .container {
21
+ display: flex;
22
+ flex-direction: row;
23
+ justify-content: space-between;
24
+ align-items: center;
25
+ }
26
+
27
+ th {
28
+ padding: config.$space-2x config.$space-3x;
29
+ white-space: nowrap;
30
+ text-align: left;
31
+
32
+ .icon {
33
+ width: config.$icon-size-3x;
34
+ height: config.$icon-size-3x;
35
+ margin-left: config.$space-1x;
36
+ > svg {
37
+ width: 100%;
38
+ height: 100%;
39
+ }
40
+ }
41
+ }
42
+
43
+ td {
44
+ padding: config.$space-5x;
45
+ }
46
+
47
+ &.primary {
48
+ thead > tr {
49
+ @include typography.cards-table-list-header;
50
+ }
51
+
52
+ th {
53
+ background: color_alias.$primary-color-600;
54
+
55
+ .icon {
56
+ > svg {
57
+ fill: color_alias.$neutral-white;
58
+ path {
59
+ fill: color_alias.$neutral-white;
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ th:first-child {
66
+ border-radius: config.$corner-radius-xxs 0px 0px config.$corner-radius-xxs;
67
+ }
68
+
69
+ th:last-child {
70
+ border-radius: 0px config.$corner-radius-xxs config.$corner-radius-xxs 0px;
71
+ }
72
+
73
+ tr {
74
+ box-shadow: 0px 0px 3px 0px rgba(0, 0, 0, 0.25);
75
+ }
76
+
77
+ tr {
78
+ td {
79
+ @include typography.cards-table-list-text;
80
+ }
81
+ }
82
+
83
+ tr:not(.disabled):hover {
84
+ background: color_alias.$primary-color-50;
85
+ }
86
+
87
+ tr.disabled {
88
+ background: color_alias.$neutral-color-50;
89
+
90
+ td {
91
+ @include typography.cards-table-list-disabled-text;
92
+ }
93
+ }
94
+
95
+ tr > td:first-child {
96
+ @include typography.cards-table-list-highlight-text;
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,69 @@
1
+ import './CardsTableList.scss'
2
+ import { Icon, IconType } from './Icon'
3
+
4
+ export type Variant = 'primary'
5
+ export type Header = { label: string; icon?: IconType }
6
+ export type Data = {
7
+ [key: string]: string
8
+ }
9
+ export type Row = { id: string; isDisabled: boolean; data: Data }
10
+
11
+ export interface CardsTableListProps
12
+ extends React.ComponentPropsWithoutRef<'table'> {
13
+ headers: Header[]
14
+ rows: Row[]
15
+ variant?: Variant
16
+ }
17
+
18
+ export function CardsTableList({
19
+ headers,
20
+ rows,
21
+ summary,
22
+ variant = 'primary',
23
+ }: CardsTableListProps): React.JSX.Element {
24
+ const cssClasses = ['cards-table-list', variant].join(' ')
25
+
26
+ return (
27
+ <table summary={summary} role="table" className={cssClasses}>
28
+ <thead role="rowgroup">
29
+ <tr role="row">
30
+ {headers.map((header) => {
31
+ const { icon } = header
32
+ return (
33
+ <th
34
+ scope="col"
35
+ role="columnheader"
36
+ className="header"
37
+ key={header.label}
38
+ >
39
+ <div className="container">
40
+ <div className="title-container">
41
+ <span>{header.label}</span>
42
+ {icon && <Icon name={icon} />}
43
+ </div>
44
+ </div>
45
+ </th>
46
+ )
47
+ })}
48
+ </tr>
49
+ </thead>
50
+ <tbody role="rowgroup">
51
+ {rows.map((row) => {
52
+ const { data, isDisabled } = row
53
+ const disabledClass = isDisabled ? 'disabled' : ''
54
+ return (
55
+ <tr role="row" className={`row ${disabledClass}`} key={row.id}>
56
+ {Object.getOwnPropertyNames(data).map((property) => {
57
+ return (
58
+ <td role="cell" key={row.id + property} className="cell">
59
+ {data[property]}
60
+ </td>
61
+ )
62
+ })}
63
+ </tr>
64
+ )
65
+ })}
66
+ </tbody>
67
+ </table>
68
+ )
69
+ }
@@ -5,9 +5,14 @@ export type IconType = keyof typeof icons
5
5
 
6
6
  export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
7
7
  name: IconType
8
+ className?: string
8
9
  }
9
10
 
10
- export const Icon: React.FC<IconProps> = ({ name, ...props }) => {
11
- const cssClasses = ['icon', name === 'Loading' ? 'rotate' : ''].join(' ')
11
+ export const Icon: React.FC<IconProps> = ({ name, className, ...props }) => {
12
+ const cssClasses = [
13
+ 'icon',
14
+ className,
15
+ name === 'Loading' ? 'rotate' : '',
16
+ ].join(' ')
12
17
  return <span className={cssClasses}>{icons[name](props)}</span>
13
18
  }
@@ -11,7 +11,7 @@
11
11
  padding-left: 12px;
12
12
  }
13
13
 
14
- &:has(.input:invalid, .input:required) {
14
+ &:has(.input.invalid) {
15
15
  & .input-help-text {
16
16
  color: color_alias.$error-color-1000;
17
17
  }
@@ -45,7 +45,7 @@
45
45
  border: 1px solid color_alias.$primary-color-1000;
46
46
  }
47
47
 
48
- &:invalid {
48
+ &.invalid {
49
49
  border: 1px solid color_alias.$error-color-1000;
50
50
  }
51
51
 
@@ -61,7 +61,7 @@
61
61
  }
62
62
 
63
63
  .input-help-text {
64
- @include typography.input-help-text;
64
+ @include typography.form-help-text;
65
65
  }
66
66
 
67
67
  .input-label {
@@ -76,8 +76,6 @@
76
76
 
77
77
  .icon {
78
78
  position: absolute;
79
- top: 0.75rem;
80
- left: 0.7rem;
81
79
  width: config.$icon-size-3x;
82
80
  height: config.$icon-size-3x;
83
81
  > svg {
@@ -85,6 +83,16 @@
85
83
  height: 100%;
86
84
  }
87
85
  }
86
+
87
+ .left-icon {
88
+ top: 0.75rem;
89
+ left: 0.7rem;
90
+ }
91
+
92
+ .password-icon {
93
+ top: 0.75rem;
94
+ right: 0.7rem;
95
+ }
88
96
  }
89
97
 
90
98
  .input {
@@ -1,4 +1,5 @@
1
1
  import './Input.scss'
2
+ import React, { useState } from 'react'
2
3
  import { Icon, IconType } from './Icon'
3
4
 
4
5
  export type InputVariant = 'primary'
@@ -10,6 +11,7 @@ export interface InputProps extends React.ComponentPropsWithoutRef<'input'> {
10
11
  helpText?: string
11
12
  variant?: InputVariant
12
13
  id: string
14
+ invalid?: boolean
13
15
  }
14
16
 
15
17
  export function Input({
@@ -22,10 +24,28 @@ export function Input({
22
24
  type = 'text',
23
25
  name,
24
26
  id,
27
+ invalid,
25
28
  ...props
26
29
  }: InputProps): React.JSX.Element {
30
+ const [showPassword, setShowPassword] = useState(false)
27
31
  const iconClass = icon ? 'with-icon' : ''
28
- const cssClasses = ['input', iconClass].join(' ')
32
+ const invalidClass = invalid ? 'invalid' : ''
33
+ const cssClasses = ['input', iconClass, invalidClass].join(' ')
34
+
35
+ function handlePasswordIcon() {
36
+ return showPassword ? 'ShowOff' : 'Show'
37
+ }
38
+
39
+ function handleInputType() {
40
+ if (type !== 'password') return type
41
+
42
+ return showPassword ? 'text' : 'password'
43
+ }
44
+
45
+ function handlePasswordVisibility() {
46
+ setShowPassword(!showPassword)
47
+ }
48
+
29
49
  return (
30
50
  <div className={`input-group ${variant}`}>
31
51
  {!hideLabel && (
@@ -34,16 +54,23 @@ export function Input({
34
54
  </label>
35
55
  )}
36
56
  <div className="input-container">
37
- {icon && <Icon name={icon} />}
57
+ {icon && <Icon className="left-icon" name={icon} />}
38
58
  <input
39
59
  id={id}
40
60
  className={cssClasses}
41
61
  disabled={disabled}
42
- type={type}
62
+ type={handleInputType()}
43
63
  name={name}
44
64
  aria-label={label}
45
65
  {...props}
46
66
  />
67
+ {type === 'password' && (
68
+ <Icon
69
+ className="password-icon"
70
+ name={handlePasswordIcon()}
71
+ onClick={handlePasswordVisibility}
72
+ />
73
+ )}
47
74
  </div>
48
75
  {helpText && <span className="input-help-text">{helpText}</span>}
49
76
  </div>
@@ -0,0 +1,148 @@
1
+ @use '../settings/color_alias';
2
+ @use '../settings/typography';
3
+ @use '../settings/config';
4
+
5
+ .multiselect-group {
6
+ display: flex;
7
+ flex-direction: column;
8
+ gap: config.$space-2x;
9
+
10
+ &:has(.selected-option.invalid) {
11
+ & .multiselect-help-text {
12
+ color: color_alias.$error-color-1000;
13
+ }
14
+ }
15
+
16
+ &.primary {
17
+ .multiselect-label {
18
+ @include typography.form-label;
19
+ }
20
+
21
+ .selected-option {
22
+ border-radius: config.$corner-radius-m;
23
+ border: 1px solid color_alias.$neutral-color-300;
24
+ background: color_alias.$neutral-white;
25
+ @include typography.select-text;
26
+
27
+ > .icon {
28
+ > svg {
29
+ fill: color_alias.$primary-color-1000;
30
+ path {
31
+ fill: color_alias.$primary-color-1000;
32
+ }
33
+ }
34
+ }
35
+
36
+ &.filled {
37
+ @include typography.select-option-text;
38
+ }
39
+
40
+ &:focus {
41
+ outline: color_alias.$primary-color-1000;
42
+ border: 1px solid color_alias.$primary-color-1000;
43
+ }
44
+
45
+ &.invalid {
46
+ border: 1px solid color_alias.$error-color-1000;
47
+ }
48
+
49
+ &.disabled {
50
+ border: 1px solid color_alias.$neutral-color-200;
51
+ background: color_alias.$neutral-color-50;
52
+ color: color_alias.$neutral-color-200;
53
+
54
+ > .icon {
55
+ > svg {
56
+ fill: color_alias.$neutral-color-200;
57
+ path {
58
+ fill: color_alias.$neutral-color-200;
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ .multiselect-options {
66
+ border-radius: config.$corner-radius-xxs;
67
+ background: color_alias.$neutral-white;
68
+ box-shadow:
69
+ 0px 9px 28px 8px rgba(0, 0, 0, 0.05),
70
+ 0px 6px 16px 0px rgba(0, 0, 0, 0.08),
71
+ 0px 3px 6px -4px rgba(0, 0, 0, 0.12);
72
+
73
+ .option {
74
+ background: color_alias.$neutral-white;
75
+ @include typography.select-option-text;
76
+
77
+ &:hover {
78
+ background-color: color_alias.$primary-color-50;
79
+ }
80
+
81
+ > .icon {
82
+ > svg {
83
+ border-radius: config.$corner-radius-xxs;
84
+ .checkbox-active_svg__border {
85
+ fill: color_alias.$primary-color-1000;
86
+ }
87
+ .checkbox-inactive_svg__border {
88
+ fill: color_alias.$neutral-color-300;
89
+ }
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ .multiselect-help-text {
96
+ @include typography.form-help-text;
97
+ }
98
+ }
99
+
100
+ .multiselect-container {
101
+ display: inline-block;
102
+ text-align: left;
103
+ position: relative;
104
+ }
105
+
106
+ .selected-option {
107
+ display: flex;
108
+ justify-content: space-between;
109
+ align-items: center;
110
+ padding: config.$space-2x config.$space-3x;
111
+ cursor: default;
112
+
113
+ > .icon {
114
+ width: config.$icon-size-3x;
115
+ height: config.$icon-size-3x;
116
+ > svg {
117
+ width: 100%;
118
+ height: 100%;
119
+ }
120
+ }
121
+ }
122
+
123
+ .multiselect-options {
124
+ margin: 0;
125
+ padding: config.$space-1x 0rem;
126
+ text-align: left;
127
+ position: absolute;
128
+ width: 100%;
129
+
130
+ .option {
131
+ display: flex;
132
+ align-items: center;
133
+ cursor: default;
134
+ list-style-type: none;
135
+ padding: config.$space-2x config.$space-3x;
136
+
137
+ > .icon {
138
+ width: config.$icon-size-4x;
139
+ height: config.$icon-size-4x;
140
+ margin-right: config.$space-1x;
141
+ > svg {
142
+ width: 100%;
143
+ height: 100%;
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }