Repository: speee/jsx-slack (ts)

Issue:
Add React-compatible camelCase prop <time dateTime>
jsx-slack has supported HTML style `<time datetime>` attribute. On the other hand, React uses camelCase prop `<time dateTime>` normally. Both will work in React but not in jsx-slack.

For compatibility of React JSX, it's good to add an alias prop `dateTime` for `datetime` attribute.

Pull request description:
Add React-compatible camelCase prop <time dateTime>
Now `<time>` HTML intrinsic element has supported React-compatible camelCased `dateTime` prop. `dateTime` is now as primary attribute of `<time>`, and previous `datetime` prop was changed to alias prop for camelCase.

Closes #179.

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 05f5774..c2d20cd 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -19,7 +19,6 @@ module.exports = {
     'no-console': 'warn',
     'react/jsx-key': 'off',
     'react/no-children-prop': 'off',
-    'react/no-unknown-property': 'off',
     'react/prop-types': 'off',
   },
   settings: {
diff --git a/demo/schema.js b/demo/schema.js
index 6cc8344..a7d9ae2 100644
--- a/demo/schema.js
+++ b/demo/schema.js
@@ -442,7 +442,10 @@ const schema = {
     attrs: {},
     children: markupHTML.filter((t) => t !== 'b' && t !== 'strong'),
   },
-  time: { attrs: { datetime: null, fallback: null }, children: [] },
+  time: {
+    attrs: { dateTime: null, datetime: null, fallback: null },
+    children: [],
+  },
   ul: { attrs: {}, children: ['li'] },
 }
 
diff --git a/docs/block-elements.md b/docs/block-elements.md
index 9c116d0..f3391e9 100644
--- a/docs/block-elements.md
+++ b/docs/block-elements.md
@@ -429,7 +429,7 @@ A container for grouping checkboxes. _This component is only for [`<Modal>`](blo
     <CheckboxGroup actionId="todo">
       <Checkbox value="xxx-0001">
         <b>Learn about Slack app</b> (
-        <time datetime={new Date(2020, 1, 24)}>{'{date}'}</time>)
+        <time dateTime={new Date(2020, 1, 24)}>{'{date}'}</time>)
         <small>
           <i>
             XXX-0001: <b>High</b>
@@ -438,7 +438,7 @@ A container for grouping checkboxes. _This component is only for [`<Modal>`](blo
       </Checkbox>
       <Checkbox value="xxx-0002">
         <b>Learn about jsx-slack</b> (
-        <time datetime={new Date(2020, 1, 27)}>{'{date}'}</time>)
+        <time dateTime={new Date(2020, 1, 27)}>{'{date}'}</time>)
         <small>
           <i>
             XXX-0002: <b>Medium</b>
@@ -448,7 +448,7 @@ A container for grouping checkboxes. _This component is only for [`<Modal>`](blo
       <Checkbox value="xxx-0003" checked>
         

           <b>Prepare development environment</b> (
-          <time datetime={new Date(2020, 1, 21)}>{'{date}'}</time>)
+          <time dateTime={new Date(2020, 1, 21)}>{'{date}'}</time>)
         

         <small>
           <i>
diff --git a/docs/html-like-formatting.md b/docs/html-like-formatting.md
index d93a5d1..0967e45 100644
--- a/docs/html-like-formatting.md
+++ b/docs/html-like-formatting.md
@@ -122,14 +122,14 @@ Of course, we also support special mentions like `@here`, `@channel`, and `@ever
 [Slack supports date formatting for localization by timezone](https://api.slack.com/messaging/composing/formatting#date-formatting), and jsx-slack can use it through HTML5 `<time>` tag.
 
 ```jsx
-<time datetime="1392734382">{'Posted {date_num} {time_secs}'}</time>
+<time dateTime="1392734382">{'Posted {date_num} {time_secs}'}</time>
 // => "<!date^1392734382^Posted {date_num} {time_secs}|Posted 2014-02-18 14:39:42 PM>"
 
-<time datetime="1392734382">{'{date} at {time}'}</time>
+<time dateTime="1392734382">{'{date} at {time}'}</time>
 // => "<!date^1392734382^{date} at {time}|February 18th, 2014 at 14:39 PM>"
 
 <a href="https://example.com/">
-  <time datetime="1392734382" fallback="Feb 18, 2014 PST">
+  <time dateTime="1392734382" fallback="Feb 18, 2014 PST">
     {'{date_short}'}
   </time>
 </a>
diff --git a/src/jsx.ts b/src/jsx.ts
index e4dabfb..b83fddb 100644
--- a/src/jsx.ts
+++ b/src/jsx.ts
@@ -699,14 +699,14 @@ export namespace JSXSlack {
        * {@link https://api.slack.com/reference/surfaces/formatting#date-formatting Learn about date formatting in Slack documentation.}
        *
        * ```jsx
-       * <time datetime="1392734382">{'Posted {date_num} {time_secs}'}</time>
+       * <time dateTime="1392734382">{'Posted {date_num} {time_secs}'}</time>
        * // <!date^1392734382^Posted {date_num} {time_secs}|Posted 2014-02-18 14:39:42 PM>
        *
-       * <time datetime={1392734382}>{'{date} at {time}'}</time>
+       * <time dateTime={1392734382}>{'{date} at {time}'}</time>
        * // <!date^1392734382^{date} at {time}|February 18th, 2014 at 14:39 PM>
        *
        * <a href="https://example.com/">
-       *  <time datetime={new Date(Date.UTC(2014, 1, 18, 14, 39, 42))} fallback="Feb 18, 2014 PST">
+       *  <time dateTime={new Date(Date.UTC(2014, 1, 18, 14, 39, 42))} fallback="Feb 18, 2014 PST">
        *    {'{date_short}'}
        *  </time>
        * </a>
@@ -717,29 +717,11 @@ export namespace JSXSlack {
        * not-compatible attribute with HTML_: `fallback` to define the fallback
        * text for not-supported Slack client.
        */
-      time: {
-        children?: ChildElements
-
-        /**
-         * Set the value of date and time to render.
-         *
-         * jsx-slack accepts either of a parsable string as date, UNIX timestamp
-         * _in second_, or JavaScript `Date` instance.
-         */
-        datetime: string | number | Date
-
-        /**
-         * Define the fallback text, may render when the client cannot parse
-         * specified date and format.
-         *
-         * If not defined, jsx-slack tries to generate the fallback text
-         * automatically from the specified date and format. _Please note that a
-         * timezone for the generated text is always UTC._
-         *
-         * __NOTE__: This prop is not-compatible with HTML.
-         */
-        fallback?: string
-      }
+      time:
+        | (TimeIntrinsicElementProps &
+            Required<Pick<TimeIntrinsicElementProps, 'dateTime'>>)
+        | (TimeIntrinsicElementProps &
+            Required<Pick<TimeIntrinsicElementProps, 'datetime'>>)
 
       /**
        * Create the unordered list.
@@ -753,6 +735,35 @@ export namespace JSXSlack {
     export interface ElementChildrenAttribute {
       children: {}
     }
+
+    type TimeIntrinsicElementProps = {
+      children?: ChildElements
+
+      /**
+       * Set the value of date and time to render.
+       *
+       * jsx-slack accepts either of a parsable string as date, UNIX timestamp
+       * _in second_, or JavaScript `Date` instance.
+       */
+      dateTime?: string | number | Date
+
+      /**
+       * An alias into `dateTime` attribute.
+       */
+      datetime?: string | number | Date
+
+      /**
+       * Define the fallback text, may render when the client cannot parse
+       * specified date and format.
+       *
+       * If not defined, jsx-slack tries to generate the fallback text
+       * automatically from the specified date and format. _Please note that a
+       * timezone for the generated text is always UTC._
+       *
+       * __NOTE__: This prop is not-compatible with HTML.
+       */
+      fallback?: string
+    }
   }
 }
 
diff --git a/src/mrkdwn/jsx.ts b/src/mrkdwn/jsx.ts
index 36c8481..7659871 100644
--- a/src/mrkdwn/jsx.ts
+++ b/src/mrkdwn/jsx.ts
@@ -84,9 +84,10 @@ const stringifyHtml = (
       return `<a${buildAttr(props)}>${content}</a>`
     }
     case 'time': {
-      const dateInt = Number.parseInt(props.datetime, 10)
+      const dateTimeStr = props.dateTime ?? props.datetime
+      const dateInt = Number.parseInt(dateTimeStr, 10)
       const date = new Date(
-        Number.isNaN(dateInt) ? props.datetime : dateInt * 1000
+        Number.isNaN(dateInt) ? dateTimeStr : dateInt * 1000
       )
       const datetime = Math.floor(date.getTime() / 1000)
       const format = text().replace(/\|/g, '\u01c0')


Test patch:
diff --git a/test/block-kit/builtin-components.tsx b/test/block-kit/builtin-components.tsx
index 826d029..7c65bd4 100644
--- a/test/block-kit/builtin-components.tsx
+++ b/test/block-kit/builtin-components.tsx
@@ -88,7 +88,7 @@ describe('Built-in components', () => {
           <Blocks>
             <Section>
               <Escape>
-                <time datetime={1234567890} fallback="fall_back">
+                <time dateTime={1234567890} fallback="fall_back">
                   {'{date_num} {time_secs}'}
                 </time>
               </Escape>
diff --git a/test/mrkdwn.tsx b/test/mrkdwn.tsx
index dc6cd46..6765deb 100644
--- a/test/mrkdwn.tsx
+++ b/test/mrkdwn.tsx
@@ -151,7 +151,7 @@ describe('HTML parser for mrkdwn', () => {
       expect(
         mrkdwn(
           <i>
-            <time datetime={1234567890} fallback="fall_back">
+            <time dateTime={1234567890} fallback="fall_back">
               {'{date_num} {time_secs}'}
             </time>
           </i>
@@ -478,7 +478,7 @@ describe('HTML parser for mrkdwn', () => {
       expect(
         mrkdwn(
           <code>
-            <time datetime="1552212000">{'{date_num}'}</time>
+            <time dateTime="1552212000">{'{date_num}'}</time>
           </code>
         )
       ).toBe('`<!date^1552212000^{date_num}|2019-03-10>`')
@@ -1147,51 +1147,75 @@ describe('HTML parser for mrkdwn', () => {
     it('converts <time> tag to mrkdwn format', () => {
       expect(
         mrkdwn(
-          <time datetime="1552212000" fallback="fallback">
+          <time dateTime="1552212000" fallback="fallback">
             {'{date_num}'}
           </time>
         )
       ).toBe('<!date^1552212000^{date_num}|fallback>')
     })
 
+    it('has aliased datetime prop into camelCase prop', () => {
+      expect(
+        mrkdwn(
+          // eslint-disable-next-line react/no-unknown-property
+          <time datetime={1552212000} fallback="fallback">
+            {'{date_num}'}
+          </time>
+        )
+      ).toBe('<!date^1552212000^{date_num}|fallback>')
+
+      // Prefers to camelCase
+      expect(
+        mrkdwn(
+          <time
+            dateTime={'1234567890'}
+            datetime={1552212000} // eslint-disable-line react/no-unknown-property
+            fallback="fallback"
+          >
+            {'{date_num}'}
+          </time>
+        )
+      ).toBe('<!date^1234567890^{date_num}|fallback>')
+    })
+
     it('generates UTC fallback text from content if fallback attr is not defined', () => {
       // 1552212000 => 2019-03-10 10:00:00 UTC (= 02:00 PST = 03:00 PDT)
-      expect(mrkdwn(<time datetime={1552212000}>{'{date_num}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{date_num}'}</time>)).toBe(
         '<!date^1552212000^{date_num}|2019-03-10>'
       )
 
-      expect(mrkdwn(<time datetime={1552212000}>{'{date}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{date}'}</time>)).toBe(
         '<!date^1552212000^{date}|March 10th, 2019>'
       )
 
-      expect(mrkdwn(<time datetime={1552212000}>{'{date_short}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{date_short}'}</time>)).toBe(
         '<!date^1552212000^{date_short}|Mar 10, 2019>'
       )
 
-      expect(mrkdwn(<time datetime={1552212000}>{'{date_long}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{date_long}'}</time>)).toBe(
         '<!date^1552212000^{date_long}|Sunday, March 10th, 2019>'
       )
 
-      expect(mrkdwn(<time datetime={1552212000}>{'{time}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{time}'}</time>)).toBe(
         '<!date^1552212000^{time}|10:00 AM>'
       )
 
-      expect(mrkdwn(<time datetime={1552212000}>{'{time_secs}'}</time>)).toBe(
+      expect(mrkdwn(<time dateTime={1552212000}>{'{time_secs}'}</time>)).toBe(
         '<!date^1552212000^{time_secs}|10:00:00 AM>'
       )
 
       // HTML entities
       expect(
-        mrkdwn(<time datetime={1552212000}>&lt;{'{date_num}'}&gt;</time>)
+        mrkdwn(<time dateTime={1552212000}>&lt;{'{date_num}'}&gt;</time>)
       ).toBe('<!date^1552212000^&lt;{date_num}&gt;|&lt;2019-03-10&gt;>')
 
       expect(
-        mrkdwn(<time datetime={1552212000}>&#123;date_num&#125; &hearts;</time>)
+        mrkdwn(<time dateTime={1552212000}>&#123;date_num&#125; &hearts;</time>)
       ).toBe('<!date^1552212000^{date_num} \u2665|2019-03-10 \u2665>')
     })
 
     test.each`
-      datetime     | format                      | contain
+      dateTime     | format                      | contain
       ${today}     | ${'{date_pretty}'}          | ${'Today'}
       ${today}     | ${'{date_short_pretty}'}    | ${'Today'}
       ${today}     | ${'{date_long_pretty}'}     | ${'Today'}
@@ -1212,8 +1236,8 @@ describe('HTML parser for mrkdwn', () => {
       ${tomorrow}  | ${'At {date_long_pretty}'}  | ${'At tomorrow'}
     `(
       'generates prettified fallback date "$contain" with format "$format"',
-      ({ datetime, format, contain }) => {
-        expect(mrkdwn(<time datetime={datetime}>{format}</time>)).toContain(
+      ({ dateTime, format, contain }) => {
+        expect(mrkdwn(<time dateTime={dateTime}>{format}</time>)).toContain(
           `|${contain}>`
         )
       }
@@ -1224,7 +1248,7 @@ describe('HTML parser for mrkdwn', () => {
 
       expect(
         mrkdwn(
-          <time datetime={date} fallback="fallback">
+          <time dateTime={date} fallback="fallback">
             <i>with</i> <b>text</b> 
formatting

           </time>
         )
@@ -1232,7 +1256,7 @@ describe('HTML parser for mrkdwn', () => {
 
       expect(
         mrkdwn(
-          <time datetime={date} fallback="fallback">
+          <time dateTime={date} fallback="fallback">
             Convert
             <br />
             line breaks
@@ -1245,7 +1269,7 @@ describe('HTML parser for mrkdwn', () => {
 
       expect(
         mrkdwn(
-          <time datetime={date} fallback="fallback">
+          <time dateTime={date} fallback="fallback">
             <blockquote>test</blockquote>
             <pre>test</pre>
             <code>test</code>
@@ -1259,7 +1283,7 @@ describe('HTML parser for mrkdwn', () => {
       expect(
         mrkdwn(
           <a href="https://example.com/">
-            <time datetime={1552212000} fallback="2019-03-10">
+            <time dateTime={1552212000} fallback="2019-03-10">
               {'{date_num}'}
             </time>
           </a>
@@ -1271,7 +1295,7 @@ describe('HTML parser for mrkdwn', () => {
       // NOTE: We have to escape brackets but Slack won't decode entities in fallback.
       expect(
         mrkdwn(
-          <time datetime={1552212000} fallback="<2019-03-10>">
+          <time dateTime={1552212000} fallback="<2019-03-10>">
             {'<{date_num}>'}
           </time>
         )
@@ -1281,7 +1305,7 @@ describe('HTML parser for mrkdwn', () => {
     it('escapes divider in contents and fallback', () => {
       expect(
         mrkdwn(
-          <time datetime={1552212000} fallback="by XXX | 2019-03-10">
+          <time dateTime={1552212000} fallback="by XXX | 2019-03-10">
             by XXX | {'{date_num}'}
           </time>
         )